A Guide to Dependency Caching and Build Caching
The two most important characteristics of continuous integration (CI) are that it first be reliable and second be fast. At Bitrise, we help mobile development teams achieve shorter CI build durations and quicker feedback loops, with caching being a key method to achieve this.
Fundamentally, caching is about avoiding work by keeping frequently needed resources easily accessible. It accelerates repeated processes by storing previously used data or outputs for quick retrieval, leading to shorter build durations. This not only saves time but also improves workflow efficiency, allowing developers to concentrate on core development tasks rather than waiting on build processes.
This blog post explores two types of caching that help us accelerate build times, dependency caching and build caching.
Dependency caching, often termed key/value dependency caching, is a method used to reduce the need for repeatedly fetching unchanged dependencies from the network during build processes. It involves storing frequently used dependencies, such as code libraries and SDKs, in a cache with unique identifiers or “keys.”
When a dependency is needed during a build, the system first checks the cache using this key. If the cached version is found, it is used directly, eliminating the need for re-downloading. This approach significantly saves time and resources, especially in large-scale projects. The key is typically a unique attribute like the dependency’s name, version, or a hash of its contents, and the value is the dependency itself.
As with most things, the answer to when you should opt to use dependency caching is it depends.
However, there are several guiding principles that we can use to determine if dependency caching might be suitable for your application:
- Application complexity: Larger applications often require measurable time to download and install dependencies, so caching can save time in the build process. Smaller applications with few dependencies may not benefit as much.
- Dependency manager behavior: Some dependency managers, such as Carthage for iOS, support caching binaries, while others, like Swift Package Manager, only save the raw source. Caching binaries offers more speed up because it avoids more work.
- Build frequency: If you’re frequently building and re-building your application in CI, dependency caching can be highly beneficial. It reduces build time for each iteration. For projects with infrequent builds, the benefits of caching may be less noticeable.
- Network performance: If your application requires dependencies from networks with limited bandwidth or inconsistent availability, dependency caching can be a benefit. It reduces the reliance on network connectivity for fetching dependencies.
- Types of dependencies: Applications that rely on large, less frequently updated libraries or frameworks are great candidates for caching. Applications that frequently change dependencies or use many small, frequently updated packages might not benefit as much, as the cache will require constant updating. This obviates the primary purpose of dependency caching.
Next, let’s unpack build caching and its comparison to dependency caching.
Build caching aims to reduce redundant work by storing outputs of intermediate build steps, like compiled code or test results, for reuse in subsequent builds. This approach prevents the need to repeat identical processes in future builds and can speed up iteration cycles.
This is, to some degree, the out-of-the-box behavior for many build systems in the local dev environment, where outputs are stored on the file system, allowing developers to make changes to a codebase without rebuilding unchanged parts. This is the concept of “minimal builds.” It significantly speeds up the build process and local feedback loop for individual developers.
Remote build caching extends this concept to team environments. Here, cached outputs are stored on an external server and shared across multiple users or build processes in different environments. This shared cache reduces build times across entire teams, allowing for the integration of cached outputs into local development, further speeding up build times and enhancing efficiency.
Benefits of Remote Build Caching
Remote build caching can benefit both individual and collaborative settings, and there are several advantages:
- Reduced CI build times: Remote build caching dramatically cuts CI build times. It’s not uncommon for Bitrise customers to see 50% reductions in their build durations. By reusing previous build outputs, it eliminates repetitive operations, speeding up the build process especially in large projects.
- Reduced local build times: Imagine working on a large project whose trunk branch is constantly updated. When you start work in the morning by pulling the latest changes, the first build of the day will take a long time. But with a remote build cache, that build will be snappy because it’s reusing work done by a different machine that built the most recent changeset.
- Resource efficiency: This caching method optimizes resource usage by reducing repetitive build tasks and lessening demand on CPU, memory, and bandwidth. This efficiency not only speeds up builds but also lowers overall resource consumption, which decreases costs.
- Enhanced developer productivity: The time savings and consistency from remote build caching boost developer productivity and morale. Developers spend less time waiting for builds and more on coding and problem-solving.
- Faster time to market: By accelerating the development process, remote build caching enables quicker release of new features and updates. The faster response to market and user feedback positively impacts company reputation, growth, and customer loyalty.
Again, it depends! Certain conditions and application types benefit the most from remote build caching. We’ll soon get into best practices for a high-performing caching implementation, but for now, here are a few general indicators we can look for:
- Application modularity: Remote build caching can benefit large projects with several decoupled modules. With build cache, a PR that changes only a few modules enables the CI job to skip building and automated testing of unaffected modules.
- Contributor count: Like dependency caching, remote build caching can dramatically speed up the process when new code is integrated at a high rate. In projects with dozens or hundreds of developers, remote build cache solves the slow-build-after-pulling-latest problem.
- Mono-repos: Mono-repos are likely to contain unrelated portions of code. Changing one portion shouldn’t require a full rebuild of all portions, and a build cache enables this.
- Resource optimization: In growth scenarios where CI costs are spiraling at O(n2) with the combined increase in contributors and test suite growth, remote build caching controls that runaway cost. By reducing redundant build operations, caching conserves resources, capping infra spend.
Dependency caching and build caching are not mutually exclusive practices. Each complements the other, and they can work in harmony.
Dependency caching stores essential external libraries for quick access during builds, while build caching preserves outputs like compiled code and tests. This combination accelerates the build process by reducing both the need for downloading external dependencies and the recompilation of unchanged application parts.
Together, dependency and build caching optimize both local and CI builds, improving the overall reliability and speed of the development cycle.
Dependency caching and build caching are valuable tools for efficient CI/CD, improving not just build speed but the entire development workflow. These methods significantly cut build and test times, boost resource efficiency, and improve the developer experience.
Ideal for complex projects with frequent builds, they lead to more productivity and quicker time to market. To learn more about Bitrise Build Cache, explore our documentation and consider starting a free Bitrise Build Cache trial to test drive it for yourself.