Ask HN: How Long Is Your CI Process?
I've seen a couple of projects using CI to build the project and run their test suite.
All of them have been interpreted languages like PHP, Python and Ruby. Their builds and tests took between 30-45 minutes. As far as project size and complexity, these were projects that were built and maintained by four person teams over the course of 3-5 years, so it's not like they were massive services with hundreds of developers.
I'm still kind of new, I worked at a couple internships and I've been working full time for a year so I might be totally wrong but I feel like these CI pipelines could be optimized to run faster.
80 comments
[ 5.2 ms ] story [ 178 ms ] threadOur current CI takes an hour because it has to build quite a complex app on iOS and Android, this happens in parallel but the Azure build nodes we use are pretty slow. Ideally it would be faster but it’s not too huge an issue in practice, we have the lint/unit tests etc. run first so the build will fail early for any glaring errors.
What is applicable to the specific project depends, same as to what is worth which effort. To a degree, of course throwing more resources at the problem helps - faster build workers, parallelized tests, ... but isn't always easily implemented on a chosen platform and costs money of course.
In projects I worked on, it varied greatly. From just a few minutes to cases where the full process took 6 hours (which then was only done as a nightly job, and individual merge requests only ran a subset of steps). I really would want <15 mins as the normal case, but it's often difficult to get the ability to do so.
Anyway as the question is "How Long Is Your CI Process", here we go!
I have two main types of pipelines, both running on a self-hosted GitLab instance which runs on an 8th-gen i3 Intel NUC. No project is particularly massive.
1. PHP Projects. Run PHPStan + unit tests on each branch. Most projects take 1-5 mins. On master, run PHPStan + unit tests, build a Docker image, and use Helm to deploy to managed Kubernetes on DigitalOcean. This takes 5-10 mins.
2. React Projects, again not massively huge, but sizable. Biggest time is to run ESLint on every branch. About 5 mins (due to very poor caching which I keep meaning to fix). On master, run ESLint, create a Docker image, and deploy to managed Kubernetes. 5-10 mins.
There are opportunities to improve this by fixing/optimising caching. Overall I'm reasonably happy with the pipeline performance. I'm also sure that upgrading the hardware would make a big difference, probably more so than fixing the caching; an i3 isn't really ideal but this machine does well overall for my small team.
That is no way to do modern computing. Things were worse back in the day (we compiled uphill, both ways), but we're not in those days any more. Buy bigger & faster servers with more RAM until the problem goes away. It won't necessarily be cheap. But if the company is too stingy, and would prefer to be pennywise and pound foolish (saving "pennies" on a server vs developer time to sit there waiting for "compiles"), you don't want to work there.
on travis for a repository that builds 14,479 objects, 67 libraries, and 456 static executables, 284 of which are test executables which are run too. If I want to run all the test binaries on freebsd openbsd netbsd rhel7 rhel5 xnu win7 win10 too, then it takes 15 additional seconds. On a real PC, building and testing everything from scratch takes 34 seconds instead of two minutes.
And that's some many millions lines of C code.
Computing is fast these days.
edit: this will blow your mind https://justine.lol/ape.html
https://github.com/spruceid/didkit/runs/2468746631
* A "complex" library (node-resque). In CI (CircleCI) we install deps, compile Typescript to JS, test on 3 versions of node, and build docs. 4 min w/ some parallelization https://app.circleci.com/pipelines/github/actionhero/node-re...
* A web server framework (actionhero): In CI(Github Actions) we install deps, compile Typescript to JS, test on 3 versions of node, and build docs. 7 min w/ some parallelization https://github.com/actionhero/actionhero/actions/runs/801273...
* A Monorepo (Grouparoo): In CI (CircleCI) we install deps, compile Typescript to JS, run migrations, check licenses, test UIs, CLI tools, Plugins, and try out a few different databases. 5 minutes with rather extreme parallelization https://app.circleci.com/pipelines/github/grouparoo/grouparo...
In my experience, the biggest wins in CI speed improvements come from parallelization. You can parallelize by either running multiple processes/containers or by running tests in parallel on the same container (jest, parallel_tests, etc)
The only times when it was long enough that it was painful it was because there was a stage that couldn't be debugged without running the build. That's invariably what I actually preferred to fix, not the total lead time.
A 45 minute sanity check to verify nothing is fucked before releasing is fine. A 45 minute debugging feedback loop is a nightmare.
Faster CI builds are typically a nice-to-have rather than a critical improvement (& doing too many nice to haves has killed many a project).
CI tech debt is very difficult to pay down, and imho not worth it unless the dollar costs are becoming excessive and you have a dedicated release or DevOps engineer who can own it as an internal product.
Our pipeline typically takes 10-30 minutes, depending on what jobs run and where cache gets used.
The longest job, at a consistent 12 minutes is our backend test job. There’s not a lot we can do to speed this up any further because a lot of the tests run agains a test db so we can’t easily run them in parallel. Perhaps if we wanted to be really clever we could use multiple test dbs.
The build for our containers is usually very quick (a few minutes) unless we modify our package requirements.txt. That happens infrequently but it triggers an install step that will increase the overall time for the job to 10-12 minutes.
The deploy phase is very quick.
We spent a bit of time optimizing this and it came down mostly to:
1. Using cache where we can.
2. Ensuring we had enough resources allocated so that jobs were not waiting or getting slowed down by lack of available cpu.
2. Making sure that each command we run is executing optimally for performance. Some commands have flags that can speed things up, or there alternate utilities that do the same thing faster. One example of the latter is that we were using pytype as our type checker, but it often took about 15 minutes to run. We swapped it out for pyright, which takes under 5.
Our pipeline runs in Jenkins and builds a docker image that runs composer installs, application copy, and that sort of thing. We also run phpunit, phpstan, phpmd, and phpcs in our pipeline. Finally the image gets pushed up to ECR.
I think that's all pretty standard stuff. TBH I'd like us to move to github actions and optimize for more staged builds in our docker images, but we have higher priorities at the moment.
* multi-million Loc
* number of projects > 50
* languages C#, C, C++, typescript
* Frameworks: .NET Framework, .NET core, .NET standard, Angular, React
* Quality tools in build: TICS, Coverity, Roslyn, custom tools (>10)
* Tests running in build: nunit, msvstestv2, jest, karma
* number of tests running in build > 5000
* package managers used: Nuget, npm
* number of packages (private and public) > 500
Still a lot I forgot now.
It all runs in approximately 45 mins for stage1 builds, stage2-4 run nightly and weekly and take much longer (>2 hours to >24 hours for long duration stage 4). Increasing stages run longer test suites, up to approx 50k or so for stage 3 and 4, more quality checks, etc.
P.s. We spend countless hours reducing our build times. In addition we have setups to split build pipelines for those who do not need the entire archive build for their dev purposes etc. Yet, CI server aways runs single-core and cold builds.
Most of that time is spent building the Docker image.
The CI pipeline does:
- Build Docker images for the project
- Run the project
- Run Shellcheck on any shell scripts
- Run flake8 to lint the code base
- Run black in check mode to ensure proper formatting
- Reset and initialize the DB
- Run test suite
That's a baseline. At this point any increase to the ~2 min is a result of running more tests but it's usually possible to run about 100 assorted tests in ~10 seconds (testing models, views, etc.).
An example of the above is here: https://github.com/nickjj/docker-flask-example
A similar pipeline with comparable tools for Rails takes ~4-5 minutes and Phoenix takes ~4-5 minutes too. You can replace "flask" with "rails" and "phoenix" in the above URL to see those example apps too, complete with GH Action logs and CI scripts. These mainly take longer due to the build process for installing package dependencies, plus Phoenix has a compile phase too.
It really depends on what you're trying to achieve and how big the project is.
Then in the main build that hopefully is deploying to a qa environment that can do more testing, bundle artifacts for whatever dependencies need them, all that kind of stuff...
That’s how ours is set up... we use Jenkins with parallel parts where possible (like build the ui while tests that hit the db are run) it’s a process that takes time to get right and time to optimize...
We’re at about 5 mins for the quick part and 8 or so for the slower part
Both of those will probably get worse as we are planning to include full ui testing on the deployed environment too
Kinda feels like a waste of time, especially if your code is well componentized and there are not many central points of failure. (and those are pretty easy to see with cursory manual testing)
30-45 minutes just for a simple test suite, even if it's PHP, Python, and Ruby - that sounds long. But without any details on exactly what's being tested, it's hard to say.
Here is a somewhat outdated talk given by our tech lead: https://www.youtube.com/watch?v=VYI3ji8aSM0. That doesn't really get into any of the CI/CD stuff though, I don't know what if any of that stuff is publicly shareable.
I feel like a lot of what you all are working on might one day osmose nicely into something CI systems and cloud-based analysis tools use to decouple themselves from individual language semantics - kind of like how MapReduce, Bazel, Kubernetes, et al found brand new use cases outside of Google's organization years after they were invented and instituted.
It is indeed a very interesting and somewhat unique problem space. Our current "killer feature" is simply powering the cross references for internal google code search / IDE / etc. But we're always thinking about ways to expand.
We are sort of trying to stuff it into CI/cloud stuff, though there are some hard problems around dealing with the enormous variety of build systems out there. It works okay in bazel/blaze, it can work for gradle or maven with a lot of work, and a few other systems. But, the amount of custom build tooling that projects use make it difficult to wire up the entrypoint for Kythe in a generic way.