Live data from Hacker News

Scaling Golang CI by Replacing actions/setup-go

cloudx.ai

11–20 of 25 posts

Re: Scaling Golang CI by Replacing actions/setup-go

#11

There are some good off-cuts that didn't make the official post, but which might be of interest to HN! It only gets a brief mention, but the cache-pruning change was an interesting one. Cache accretion happens in the default actions/setup-go too, but dramatically increasing the number of cache-writes for cloudx-io/setup-go made it an actual issue. As the cache grows, so does the time it takes to load it from GitHub's…

I also had fun substantiating the claim "86% of actions/setup-go test runs are unnecessary." We had a general sense things were faster, and we measured impacts immediately after we made changes, but hard to understand long-run performance vs. a counterfactual.

The trick was to run back over our git history and calculate, for each commit,

1. The test package Go cache keys at that point

2. The GitHub actions/cache keys constructed by actions/setup-go and cloudx-io/setup-go respectively

Once you have these mappings, you can

1. Pick some arbitrary HEAD commit

2. Model which prior GitHub cache blob would be loaded under each action

3. Compare the test package Go cache keys in that loaded blob against those for HEAD to determine which test packages would run vs. skip

Might write this up in greater depth sometime soon.

Re: Scaling Golang CI by Replacing actions/setup-go

#12

how does this compare to WillAbides/setup-go-faster? https://github.com/WillAbides/setup-go-faster

They target different parts of actions/setup-go for optimization:

- WillAbides/setup-go-faster speeds up the Go toolchain setup (literally installing Go)

- cloudx-io/setup-go uses the slower actions/setup-go toolchain setup, but changes cache strategy so your `go test` and `go build` steps do less work

Those strategies are compatible. I hadn't heard of setup-go-faster — thanks for putting me on to it.

If you're deciding between one or the other, it'll probably come down to which inefficiency predominates in your codebase (i.e. how many tests you have, how quickly they run, and how much real churn there is in your test package build graph).

Re: Scaling Golang CI by Replacing actions/setup-go

#14
post #7

Earlier quoted context omitted.

Good question — we'd be happy to submit a PR, but it's not clear to me that they'd be interested. Some background: - Our approach writes new cache entries all the time. This can get expensive, and is a pretty big change in behavior from how actions/setup-go works today. - actions/setup-go can basically be considered incredibly critical infrastructure for the public golang ecosystem. Any change in behavior is probably…

actions/setup-go maintainer here! Great post, I like the ideas in there. We are always open to improvements, but it's true that low-risk ones are preferred. Feel free to submit your ideas to the GitHub issue tracker. I'll do some due diligence myself.

Thanks for reading :) Broken down by key idea:

- Allowing actions/setup-go users to specify a cache key prefix so that they can have more than one golang CI job, each with its own cache: this is 100% worth upstreaming. I believe there are existing requests and PRs about this. Up to you guys to implement however you see fit.

- Allowing "always update the cache": also a good idea to enable as an option, very important for non-open-source teams that are trying to maximize cache hit rate.

- Allowing "trim the cache": if you're going to allow always updating the cache, probably a good idea.

But the "always update" and "trim" cache changes combine to have a lot of risks regarding cache poisoning that might be bad for open source projects. Lukas may have a different opinion or more to say on this front.

Re: Scaling Golang CI by Replacing actions/setup-go

#15

I always advocate having custom-built docker images for CI, periodically refreshed for security fixes. CI should not run more than few seconds over the standard time to run the same thing from a dev machine. However, other people around me are fine with apt installs and pip installs from global mirrors in every CI run. So I may be just autistic.

Yeah, that drives me bonkers. Just set up a separate CI/CD for the images themselves that update daily as needed.

Re: Scaling Golang CI by Replacing actions/setup-go

#16

There are some good off-cuts that didn't make the official post, but which might be of interest to HN! It only gets a brief mention, but the cache-pruning change was an interesting one. Cache accretion happens in the default actions/setup-go too, but dramatically increasing the number of cache-writes for cloudx-io/setup-go made it an actual issue. As the cache grows, so does the time it takes to load it from GitHub's…

The way caches are managed in CI is bonkers (at least in GitLab, but GitHub probably does the same). I guess it's built to conveniently work with the simplest of projects. I had to reimplement the mechanism by hand just to get basic things working, like not downloading the entire cache when only a few entries will be used for each build

Re: Scaling Golang CI by Replacing actions/setup-go

#17

I always advocate having custom-built docker images for CI, periodically refreshed for security fixes. CI should not run more than few seconds over the standard time to run the same thing from a dev machine. However, other people around me are fine with apt installs and pip installs from global mirrors in every CI run. So I may be just autistic.

Sure. Although you can expect build/test time to take a while on bigger projects, so I think it washes out a lot of the time.

Re: Scaling Golang CI by Replacing actions/setup-go

#18
post #7

Earlier quoted context omitted.

Good question — we'd be happy to submit a PR, but it's not clear to me that they'd be interested. Some background: - Our approach writes new cache entries all the time. This can get expensive, and is a pretty big change in behavior from how actions/setup-go works today. - actions/setup-go can basically be considered incredibly critical infrastructure for the public golang ecosystem. Any change in behavior is probably…

actions/setup-go maintainer here! Great post, I like the ideas in there. We are always open to improvements, but it's true that low-risk ones are preferred. Feel free to submit your ideas to the GitHub issue tracker. I'll do some due diligence myself.

I'd echo Peter's #1 recommendation:

> - Allowing actions/setup-go users to specify a cache key prefix so that they can have more than one golang CI job, each with its own cache [...]

I'd actually go further: this may be a sensible default behavior.

"Always update the cache" can get expensive, but it's a neat one; "trim the cache" is definitely necessary if you enable this in a moderately active repository in our experience.

If you want really out-there ideas: rather than storing and loading the full cache monolithically, you could use a GitHub-specific GOCACHEPROG and Go-specific cache service to load only the active items. The pruning problem goes away because accretion is cheap. In theory, parallel jobs could actually share this joint cache. (This may not be a realistic initiative at GitHub.)

If you can raise feedback with your colleagues —

- The docs and settings for Actions Cache limits are really hard to navigate; at some pointed we desperately wanted to pay GitHub more money for more cache, but couldn't figure out why we were capped.

- Bulk-data endpoints for Actions performance would be a boon for optimization projects like this. I wind up either scraping `gh run` (slow) or setting up a GitHub App to collect perf data through webhooks (initially tedious, has to be continuously available).

All this aside — actions/setup-go is a pretty well-considered default and an essential part of writing Go on GitHub; ty for your work maintaining it!

Re: Scaling Golang CI by Replacing actions/setup-go

#19
I am setting cache to `false` and directly use actions/cache:

    - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
      if: ${{ inputs.setup-go == 'true' }}
      with:
        path: |
          ${{ env.gocache }}
          ${{ env.gomodcache }}
        key: ${{ runner.os }}-${{ runner.arch }}-go${{ steps.go-setup.outputs.go-version }}-${{ hashFiles('go.sum') }}-${{ env.today }}
        restore-keys: |
          ${{ runner.os }}-${{ runner.arch }}-go${{ steps.go-setup.outputs.go-version }}-${{ hashFiles('go.sum') }}-
          ${{ runner.os }}-${{ runner.arch }}-go${{ steps.go-setup.outputs.go-version }}-
You get one new cache every day, and you can still load the most recent one if you are the first run today.
Post reply on HN