I'm a bit lost on how to improve CI pipeline perfo...
# general
g
I'm a bit lost on how to improve CI pipeline performance. I have a CI pipeline which first runs something like:
Copy code
pants \
  --changed-since=origin/main \
  --changed-dependents=transitive \
  --filter-target-type=docker_image
  list
Then I dynamically generate one CI job per
docker_image
target to run `pants package`/`pants publish` against, etc. The issue is that the
pants list
command takes about 2 minutes in our monorepo. I was hoping to figure out some trick to reduce the time it takes to generate a list of changed
docker_image
targets. Does anyone have any creative workarounds? I am already using a remote cache (bazel-remote-cache) but it doesn't seem to help. pants version: 2.24.1
b
pants list
taking such a long time is quite surprising to me, as it does very little work (certainly not much interesting in terms of dependency inference or running external processes) To make the debugging faster, can you reproduce the slowness locally?
g
How large is your codebase? I do similar checks in multiple locations in our CI, and it's not even noticeable. What languages are involved?
g
9k python source files and about 100 docker_image targets.
@broad-processor-92400 Yes I can reproduce locally.
g
Does this change significantly depending on what flags you provide?
Copy code
$ hyperfine  \
    --setup 'pkill pantsd' \
    --warmup 1 \
    'pants list --changed-since=origin/main' \
    'pants list --changed-since=origin/main --changed-dependents=transitive' \
    'pants list --changed-since=origin/main --filter-target-type=docker_image' \
    'pants list --changed-since=origin/main --changed-dependents=transitive --filter-target-type=docker_image'
For me, it gives the following results:
Copy code
Benchmark 1: pants list --changed-since=origin/main
  Time (mean ± σ):     744.8 ms ±  54.2 ms    [User: 1.3 ms, System: 17.8 ms]
  Range (min … max):   691.3 ms … 857.7 ms    10 runs

Benchmark 2: pants list --changed-since=origin/main --changed-dependents=transitive
  Time (mean ± σ):     868.6 ms ±  58.8 ms    [User: 6.0 ms, System: 27.5 ms]
  Range (min … max):   813.0 ms … 1027.1 ms    10 runs

Benchmark 3: pants list --changed-since=origin/main --filter-target-type=docker_image
  Time (mean ± σ):     708.3 ms ±  38.3 ms    [User: 5.5 ms, System: 12.6 ms]
  Range (min … max):   667.1 ms … 787.6 ms    10 runs

Benchmark 4: pants list --changed-since=origin/main --changed-dependents=transitive --filter-target-type=docker_image
  Time (mean ± σ):     863.3 ms ±  59.1 ms    [User: 4.1 ms, System: 12.2 ms]
  Range (min … max):   811.9 ms … 1022.5 ms    10 runs

Summary
  pants list --changed-since=origin/main --filter-target-type=docker_image ran
    1.05 ± 0.10 times faster than pants list --changed-since=origin/main
    1.22 ± 0.11 times faster than pants list --changed-since=origin/main --changed-dependents=transitive --filter-target-type=docker_image
    1.23 ± 0.11 times faster than pants list --changed-since=origin/main --changed-dependents=transitive
Your codebase is about one order of magnitude larger for both target types, but it takes ~100x to run supposedly. So something scales poorly, just from comparing our numbers.
g
So one thing to know... We have 100+ python source roots with about 7 resolves. Many of the python packages/source roots are in 2 or more resolves.
I'm running the benchmark now, to be clear.
Oh my gosh.. I just realized that --changed-dependents=transitive is probably completely unnecessary lol
Going from 2 minutes down to 15 seconds should help.
or do I need that? Not sure... 🤔
results so far... running on Apple M2 Pro.
Copy code
Benchmark 1: pants list --changed-since=origin/main
  Time (mean ± σ):     14.270 s ±  1.401 s    [User: 0.018 s, System: 0.024 s]
  Range (min … max):   13.237 s … 17.769 s    10 runs

Benchmark 2: pants list --changed-since=origin/main --changed-dependents=transitive
  Time (mean ± σ):     55.710 s ±  2.416 s    [User: 0.622 s, System: 0.094 s]
  Range (min … max):   51.785 s … 59.991 s    10 runs

Benchmark 3: pants list --changed-since=origin/main --filter-target-type=docker_image
  Time (mean ± σ):     11.551 s ±  1.017 s    [User: 0.019 s, System: 0.022 s]
  Range (min … max):   10.510 s … 13.449 s    10 runs

Benchmark 4: pants list --changed-since=origin/main --changed-dependents=transitive --filter-target-type=docker_image
  Time (mean ± σ):     56.476 s ±  3.808 s    [User: 0.640 s, System: 0.101 s]
  Range (min … max):   50.772 s … 64.038 s    10 runs

Summary
  pants list --changed-since=origin/main --filter-target-type=docker_image ran
    1.24 ± 0.16 times faster than pants list --changed-since=origin/main
    4.82 ± 0.47 times faster than pants list --changed-since=origin/main --changed-dependents=transitive
    4.89 ± 0.54 times faster than pants list --changed-since=origin/main --changed-dependents=transitive --filter-target-type=docker_image
ah, no. We definitely need
--changed-dependents=transitive
My guess is we have way more targets in general.
Copy code
$ pants list //:: | wc -l
27443
Copy code
$ pants list --filter-target-type=docker_image //:: | wc -l
100
Copy code
$ pants list --filter-target-type=python_requirement //:: | wc -l
1448
Copy code
$ pants list --filter-target-type=python_source //:: | wc -l
8974
Copy code
$ pants list --filter-target-type=python_test //:: | wc -l
2106
The "Map all targets to their dependents" takes ~27 seconds.
oh... I might have just found it...
👀 1
hmm...
I created a macro that does some funky stuff...
removing it to see.
oh boy...
nevermind, only about 20%..
We have so many targets because of the number of resolves.
So the 9K files ends up becoming a lot more targets
I'd guess it's close to 18K python_source targets in total.
With a few tricks I got it down from 55s to 40s, but it's still a ton of time in comparison to the performance you're reporting.
g
Yeah, but my stats is ~1k sources, a dozen containers, and ~100 reqs.
Is the map_all_targets mostly unchanged with those tricks?
g
It went down from 27s to 24s (eye balling aka watching the counter)
g
Ok, still sizeable. How many direct changes are you running this test on?
g
I'm doing a --changed-since=HEAD~250 for testing
> How many direct changes are you running this test on? I just realized I may not understand your question
g
No, that's plenty clear 😛 I'm reading the Pants source, and one could imagine inverting the queries, so instead of starting from changed files and finding all dependents, we find all targets of a specific type and see if they depend on the changed files. I think for this specific query it would be hugely beneficial. That would avoid the dependents-mapping step.
But imagine if you had 100k docker images and changed a single python file, it's the other degenerate case and we're still slow.
🙂 1
💯 1
g
ahh, yes that does seem like a more efficient approach.
But does pants internal lib even have methodology for doing the reverse or is it very dependent on the mapping being built in the one direction?
g
The way it's done for --dependents=transitive is only used by the
changed
backend, as far as I can tell. Dependencies, on the other hand, is queried everywhere. So the question is just how the scaling works out.
g
So another thought/question: Is there a way to tell pants to ignore an entire tree of the monorepo? In our case we have very distinct parts of our monorepo and some areas do not need to be considered when checking for these changes in docker targets.
g
https://github.com/pantsbuild/pants/issues/14243,
--filter-address-regex
in theory
g
@gorgeous-winter-99296 Thanks for the back and forth. Sad there is no solution for repositories with such high target counts.
I am curious if the performance would be different if it were in rust vs python. I don't know what the code is actually doing.
Once I migrate to the new Docker containerd image store and get it working, I think the best approach will be to use --cache-from/--cache-to aggressively. Building every time and letting the cache handle it should be faster than detecting changes and deciding whether to retag or rebuild.
Sadly my initial attempt to migrate to the new containerd image store failed. A bunch of random issues that I couldn't diagnose and google and chatgpt had nothing.
g
It's an interesting point. I wonder if this code could be migrated. @happy-kitchen-89482 might've already thought about the specs handling/filtering, he's on a long-running quest to move options handling etc to Rust. The specific rule you mentioned above is here: https://github.com/pantsbuild/pants/blob/main/src/python/pants/backend/project_info/dependents.py#L42-L65, for example. That is coming from here: https://github.com/pantsbuild/pants/blob/b7f08e9926d84f201f87e56dd643e105de5f1c4b/src/python/pants/vcs/changed.py#L70-L100 That block is the major diff caused by --dependents=transitive.
👀 1
Can you file an issue for this? I couldn't get my inversed logic to yield identical results to the current impl, so having a place to track this for future work seems good.
https://github.com/pantsbuild/pants/issues/21619 seems like a similar issue, potentially
c
Does anyone have any creative workarounds?
To clarify since you mentioned you can reproduce locally, does having
pantsd
effect performance? If you do
pants list ::
and then the changed calculation, is there any change in performance? Wild Speculation: Does the new rust Dockerfile parser help at all?
👀 1
g
@curved-manchester-66006 The new rust dockerfile-parser did not impact performance.
Copy code
$ hyperfine  \
    --warmup 1 --runs 2 --setup 'pkill pantsd ||:' \
    --prepare 'pants list //::' 'pants list --changed-since=HEAD~250' \
    --prepare 'pants list //::' 'pants list --changed-since=HEAD~250 --changed-dependents=transitive' \
    --prepare 'pants list //::' 'pants list --changed-since=HEAD~250 --filter-target-type=docker_image' \
    --prepare 'pants list //::' 'pants list --changed-since=HEAD~250 --changed-dependents=transitive --filter-target-type=docker_image'
Benchmark 1: pants list --changed-since=HEAD~250
  Time (mean ± σ):     11.909 s ±  0.229 s    [User: 0.035 s, System: 0.081 s]
  Range (min … max):   11.747 s … 12.071 s    2 runs

Benchmark 2: pants list --changed-since=HEAD~250 --changed-dependents=transitive
  Time (mean ± σ):     51.341 s ±  0.212 s    [User: 0.098 s, System: 0.276 s]
  Range (min … max):   51.191 s … 51.491 s    2 runs

Benchmark 3: pants list --changed-since=HEAD~250 --filter-target-type=docker_image
  Time (mean ± σ):     11.730 s ±  0.003 s    [User: 0.020 s, System: 0.025 s]
  Range (min … max):   11.729 s … 11.732 s    2 runs

Benchmark 4: pants list --changed-since=HEAD~250 --changed-dependents=transitive --filter-target-type=docker_image
  Time (mean ± σ):     50.657 s ±  0.183 s    [User: 0.018 s, System: 0.024 s]
  Range (min … max):   50.527 s … 50.787 s    2 runs

Summary
  pants list --changed-since=HEAD~250 --filter-target-type=docker_image ran
    1.02 ± 0.02 times faster than pants list --changed-since=HEAD~250
    4.32 ± 0.02 times faster than pants list --changed-since=HEAD~250 --changed-dependents=transitive --filter-target-type=docker_image
    4.38 ± 0.02 times faster than pants list --changed-since=HEAD~250 --changed-dependents=transitive
It's still being hung on building the dependencies which @gorgeous-winter-99296 pointed out the code paths.
@curved-manchester-66006 It's still being slowed down by "Map all targets to their dependents" which @gorgeous-winter-99296 pointed out the code paths for.