How to Optimize CircleCI config

Han
4 min readApr 10, 2022

CircleCI is one of the most popular continuous integration software. It is basically a script runner that can be triggered by your github/gitlab push.

If you haven’t know about it, and you are still doing manual deploy to server, check it out now! There is a free plan! (for 6000 build minutes per month).

Once you setup the circleCI with your Github repo, whenever you push a change to the repo, circleCI will run a script that you specified to test your repo, and even deploy to cloud server.

Standard CicleCI setup:

A standard setup of a web app (dev/staging/prod) would generally include:

  1. A test job that runs tests, a deploy-staging job that deploy repo to a staging environment, a deploy-master job that deploy to production environment.
  2. run test job whenever there is a push to any branch, run test and deploy jobs whenever there are commit (most likely a merge-request) to staging or master branch.

3. A more complex web app, such as a mono repo, should have multiple test jobs, like test-frontend, test-backend .

Optimize the setup

Obviously, we want want the circleCI to be as efficient as possible, this is not to say I am cheap (I am, but that’s not the point), this is also about development productivity.

  1. Shorter turnoever. Developers will know sooner that their changes failed test.
  2. Faster deployment. Any merge-request (push feature to master branch), is deployed faster.
  3. Most importantly, budget. As CircleCI, (any many other services) charge by minutes, obviously less time is cheaper.

There are multiple directions to approach this “do not run unnecessary tests”.

1. Cancel previous run if a the branch is pushed a new commit.

Luckily, CircleCI’s platform already does this. If previous job is still running when you commit a new change to the branch, the previous job is automatically canceled and a new job with the most recent commit will start.

2. Cache test result.

Every code commit should trigger a test run, no problem, but over the years, we have discovered, there are many non-code changes, such as .gitignore , package.json change package name, config changes that does not affect how code runs. If you put frontend and backend code together, you would want frontend changes to not run backend tests and vice versa. Therefore, it is a pretty important thing to add that significantly reduced our circleCI usage. (over 60%).

The idea is simple, in the circle config, we want to run tests only if a specific folder’s files are changed. CircleCI support a cache mechanism that allows you to store a folder and allow it to be loaded across all the jobs.

In the example code above, first, I created acode_md5.txt file by taking md5 of all files in src directory. Then, I use the restore_cache + save_cache syntax in circleCI that restores the test result in jestCov/ directory. It cache key is a long text with the checksum of the code_md5.txt file. In other words, if all the code files in src directory are unchanged, then code_md5.txt should be unchanged, the cache should be correctly found and lcov file in jestCov/ should be restored. In any case, the test run command is combined with a bash if so that only if the lcov file does not exist, will it run the test.

This combination of “build md5 of all code”, “ restore test result”, “run test only if test result is not found” allows the circleCI to skip a test if all the dependent files are not changed.

This same idea could also be applied to different kind of tests. For example, you can split up unit tests by modules, so if code in one module is modified, all other modules tests are not run.

3. Cache package installation.

To run tests in circleCI environment, we must include many steps to prepare for the tests. Steps like yarn install, pip install usually takes quite a long time. So obviously we would like to cache it if possible. One approach is to cache the cache. Little twisted right ? It means we want to use the CircleCI’s cache to store the yarn cache directory. Then load it before we run yarn install, this way, we don’t have to download all the packages, instead directly copy from yarn cache.

Summary

This post showed a way to config CircleCI to skip tests if the code is unchanged. In most cases, a feature update only modifies a couple of files, so you may save a lot of time by skipping unit tests in other modules. (Still should run integration test though).

Now if you keep tabs on new technology, you might know about this monorepo framework NX.js, which build dependency graphs of your code so that it is smart enough to know the exact tests that are affected by your changes. However, the nx.js, in my own opinion, has a lot of compatibility issues with all the existing frameworks out there, it actually create a lot of problems. So I decided to manually split up tests.

That’s it for now, I will come back soon with more tricks with CircleCI .

--

--

Han

Google SWE | Newly Dad | Computational Biology PhD | Home Automation Enthusiast