Skip to content

Windows build times in the CI are highly variable #4537

Open

Description

@sxa

As part of the analysis I was doing for coming up with some CI improvement ideas for #4529 I noted that the Windows builds were extremely variable in terms of build times. While the Linux build tend to run in about 5-30 minutes (it's typically dwarfed by the test time which is now running at close to half an hour) the Windows builds are taking between 20 minutes (best case) but most days there are a few that seem to take over 2 hours for just the compile phase. win-vs2022_clang / win-vs2022_clang-arm64

Creating this issue so we can investigate whether there is any scope for improvement of the build times since (albeit subjectively) I've seen more occurrences of queued windows jobs in the CI recently than other platforms, and the spikiness of the compile jobs could potentially explain why.

I'll also note that here (because it's windows, although it's a different problem) there seems to be a relatively high proportion of failures occurring in the Windows js test suites that should perhaps also be looked at to see if any improvement to flakiness can be done. As an example https://ci.nodejs.org/job/node-test-binary-windows-js-suites/RUN_SUBSET=1,nodes=win10-COMPILED_BY-vs2022_clang/44048/console had one failure in test-sqlite-session. As noted in the CI improvement ideas issue we already have re-run testing but in this case (a 1/10 failure) the job is still marked as a fail so we possibly need to look at utilising this data as each failure in the js-suites one would likely have resulted in a PR test re-run.

08:46:45  failed 1 out of 10
08:46:45 not ok 1056 parallel/test-sqlite-session

That failure was a 08:46:45 # Fatal JavaScript out of memory: GC during deserialization condition - it would be useful to know how many of the other intermittent failures are similar problems.

Activity

  1. sxa commented on Oct 23, 2026

    @sxa
    MemberAuthor

    I wonder if this is just a side effect of having six machines each of which do one build for each of arm and x64. In the case where a V8 update or patch is done (causing the preprocessor directive for the V8 version to be bumped) that will potentially require 12 "slow" compile jobs to occur in the system (plus the debug job but that is typically not slow). While some other platforms have 3 executors (Same 3:1 ratio of machines to rebuilds) Windows has the disadvantage that the fanned test jobs could get delayed if we get a backlog of compile jobs in the queues waiting to get built that end up being run first.

    Since we already have split build/test labels (e.g. win-vs2022-clang-arm64 vs win2022-COMPILED_BY=vs2022 I wonder if it's worth trying to disable the build labels on a couple of the machines and see if that prevents larger backlogs building up by having fewer builds on invalidated caches. Noting as a potential counterargument this will leave 4 build machines which, in the case of a V8 PR test or update, would tie up 3/4 of the executors for some time (x64, arm, debug) meaning that other node-test-commit jobs will have to share the one remaining node for a while, but overall my gut feel is that it would still be a positive.

  2. sxa commented on Oct 23, 2026

    @sxa
    MemberAuthor

    At the time of writing there are four windows compile jobs for each architecture in the queue waiting for a win-vs2022_clang[-arm64] executor with an example job of https://ci.nodejs.org/job/node-test-commit/93504/ having completed all other platforms in 1h12 with windows not having started the compile yet. It is Job 81377 in the table below which took 7h23 in total including 2h34 compiling, 2h26 testing, and the rest presumably lost in queueing.

    Recent jobs were as follows. This only includes Node v27 builds:

    fanned# fanned ntprnw compile# x64 Duration x mc arm Duration arm mc js-suites# js duration
    81368 69896 3h52 rs-1 7h15 rs-4 n/a
    81370 6h45 2h03(lc) 69898 2h07 rs-3 3h51 rs-6 35676 2h28
    81373 7h46 1h28(xl) 69901 29m rs-1 4h07 rs-3 35678 1h32
    81374 7h04 1h10(xl) 69902 2h31 rs-2 1h07 rs-5
    81377 7h23 1h12(xl) 69905 2h34 rs-4 19m rs-5 44080 2h26
    81379 7h11 1h16(xl) 69907 2h11 rs-5 19m rs-6 44082 2h31
    81380 5h29 1h17(xl) 69902 28m rs-1 19m rs-6 44079 2h05
    81381 7h48 1h38(osx) 69909 19m rs-2 19m rs-6 44083 1h41
    81383 5h09 1h20(xl) 69911 20m rs-4 22m rs-6 44081 2h30
    81384 8h42 7h02(xl) 69912 2h05 rs-5 3h41 rs-6 44081 3h32 (V8 PR)
    81389 8h50 4h03(al) 69917 21m rs-1 7h25 rs-2 44092 24m
    81391 6h05 1h37(xl) 69919 28m rs-4 28m rs-4 44086 2h58
    81392 6h21 1h10(xl) 69920 20m rs-4 18m rs-6 44087 2h54(†)
    81394 6h19 1h24(osx) 69922 18m rs-3 18m rs-6 44090 39m
    81396 6h53 2h09(xl) 69924 29m rs-5 28m rs-5 44083 19m

    (*) - ntprnw is the run time of the slowest non-windows platform from the parent note-test-pull-request job.
    (†) - Job 81392 is notable because it had fast build times (once they got scheduled) but took 2h54 for the testing phase. From the twisty below which shows the log from the js-suites job you can see that the

    Log snippets from fanned job 81392
    23:53:44 Counting node-compile-windows. enabledIndex=1
    23:53:44     >> Job status: [node-compile-windows-debug] the 'build only if scm changes' feature is disabled.
    23:53:44 Triggering node-compile-windows-debug. Condition was evaluated to true.
    23:53:44 Starting build job [node-compile-windows-debug](https://ci.nodejs.org/job/node-compile-windows-debug/) at 18:53:44.
    23:53:44 Counting node-compile-windows-debug. enabledIndex=2
    23:53:44 node-compile-windows-debug: Quiet period groovy=[index < 5 ? 0 : 2 * 60], index=2 -> quietPeriodGroovy=0
    03:20:20 quiet period for node-compile-windows-debug is 0 seconds.node-compile-windows: Quiet period groovy=[index < 5 ? 0 : 2 * 60], index=1 -> quietPeriodGroovy=0
    03:20:20 quiet period for node-compile-windows is 0 seconds.Finished Build : [#35509](https://ci.nodejs.org/job/node-compile-windows-debug/35509//) of Job : [node-compile-windows-debug](https://ci.nodejs.org/job/node-compile-windows-debug/) with status : [SUCCESS](https://ci.nodejs.org/job/node-compile-windows-debug/35509//console) at 22:08:45
    03:20:20 Finished Build : [#69920](https://ci.nodejs.org/job/node-compile-windows/69920//) of Job : [node-compile-windows](https://ci.nodejs.org/job/node-compile-windows/) with status : [SUCCESS](https://ci.nodejs.org/job/node-compile-windows/69920//console) at 22:20:20
    03:20:20     >> Job status: [node-test-binary-windows-js-suites] the 'build only if scm changes' feature is disabled.
    03:20:20 Starting build job [node-test-binary-windows-js-suites](https://ci.nodejs.org/job/node-test-binary-windows-js-suites/) at 22:20:20.
    03:20:20 Counting node-test-binary-windows-js-suites. enabledIndex=1
    03:20:20     >> Job status: [node-test-binary-windows-native-suites] the 'build only if scm changes' feature is disabled.
    03:20:20 Starting build job [node-test-binary-windows-native-suites](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/) at 22:20:20.
    03:20:20 Counting node-test-binary-windows-native-suites. enabledIndex=2
    03:20:20 node-test-binary-windows-native-suites: Quiet period groovy=[index < 5 ? 0 : 2 * 60], index=2 -> quietPeriodGroovy=0
    06:15:14 quiet period for node-test-binary-windows-native-suites is 0 seconds.node-test-binary-windows-js-suites: Quiet period groovy=[index < 5 ? 0 : 2 * 60], index=1 -> quietPeriodGroovy=0
    06:15:14 quiet period for node-test-binary-windows-js-suites is 0 seconds.Finished Build : [#35689](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/35689//) of Job : [node-test-binary-windows-native-suites](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/) with status : [SUCCESS](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/35689//console) at 01:09:54
    06:15:14 Finished Build : [#44087](https://ci.nodejs.org/job/node-test-binary-windows-js-suites/44087//) of Job : [node-test-binary-windows-js-suites](https://ci.nodejs.org/job/node-test-binary-windows-js-suites/) with status : [SUCCESS](https://ci.nodejs.org/job/node-test-binary-windows-js-suites/44087//console) at 01:15:14
    

    So you can see from this that it was about 3½ hours for the compile job and nearly 3 hours for the completion of the test job. Looking in more details at the compile job you can see that while both x64 and arm completed in about 20 minutes they were held up before they started leading to the big gaps in the log:

    23:53:44 Skipping win-vs2022 for Node.js 27
    23:53:44 Skipping win-vs2022-arm64 for Node.js 27
    23:53:44 Running nodes
    23:53:44 Triggering [node-compile-windows » win-vs2022_clang-arm64](https://ci.nodejs.org/job/node-compile-windows/nodes=win-vs2022_clang-arm64/)
    23:53:44 Triggering [node-compile-windows » win-vs2022_clang](https://ci.nodejs.org/job/node-compile-windows/nodes=win-vs2022_clang/)
    03:16:17 Completed [node-compile-windows » win-vs2022_clang-arm64](https://ci.nodejs.org/job/node-compile-windows/nodes=win-vs2022_clang-arm64/) SUCCESS
    03:20:20 Completed [node-compile-windows » win-vs2022_clang](https://ci.nodejs.org/job/node-compile-windows/nodes=win-vs2022_clang/) SUCCESS
    03:20:20 All downstream projects complete!
    

    You can see this from the initial timestamps in the logs. Here's x64:

    03:00:10 Started by upstream project "[node-compile-windows](https://ci.nodejs.org/job/node-compile-windows/)" build number 
    [69920](https://ci.nodejs.org/job/node-compile-windows/69920)
    

    and arm:

    02:57:27 Started by upstream project "[node-compile-windows](https://ci.nodejs.org/job/node-compile-windows/)" build number 
    [69920](https://ci.nodejs.org/job/node-compile-windows/69920)
    

    It's a similar store on the test suites (I'll show native hear rather than js because it's simpler without the fanned subsets):

    03:20:21 Running nodes
    03:20:21 Triggering [node-test-binary-windows-native-suites » win10-vs2019-COMPILED_BY-vs2022_clang](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win10-vs2019-COMPILED_BY-vs2022_clang/)
    03:20:21 Triggering [node-test-binary-windows-native-suites » win11-vs2022-arm64-COMPILED_BY-vs2022_clang-arm64](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win11-vs2022-arm64-COMPILED_BY-vs2022_clang-arm64/)
    03:20:21 Triggering [node-test-binary-windows-native-suites » win11-vs2022-COMPILED_BY-vs2022_clang](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win11-vs2022-COMPILED_BY-vs2022_clang/)
    03:20:21 Triggering [node-test-binary-windows-native-suites » win2022-vs2022-COMPILED_BY-vs2022_clang](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win2022-vs2022-COMPILED_BY-vs2022_clang/)
    03:20:21 Triggering [node-test-binary-windows-native-suites » win2019-vs2019-COMPILED_BY-vs2022_clang](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win2019-vs2019-COMPILED_BY-vs2022_clang/)
    03:31:30 Completed [node-test-binary-windows-native-suites » win10-vs2019-COMPILED_BY-vs2022_clang](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win10-vs2019-COMPILED_BY-vs2022_clang/) SUCCESS
    03:32:45 Completed [node-test-binary-windows-native-suites » win11-vs2022-arm64-COMPILED_BY-vs2022_clang-arm64](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win11-vs2022-arm64-COMPILED_BY-vs2022_clang-arm64/) SUCCESS
    03:33:58 Completed [node-test-binary-windows-native-suites » win11-vs2022-COMPILED_BY-vs2022_clang](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win11-vs2022-COMPILED_BY-vs2022_clang/) SUCCESS
    06:09:54 Completed [node-test-binary-windows-native-suites » win2022-vs2022-COMPILED_BY-vs2022_clang](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win2022-vs2022-COMPILED_BY-vs2022_clang/) SUCCESS
    06:09:54 Completed [node-test-binary-windows-native-suites » win2019-vs2019-COMPILED_BY-vs2022_clang](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/nodes=win2019-vs2019-COMPILED_BY-vs2022_clang/) SUCCESS
    06:09:54 All downstream projects complete!
    

    And looking at the first line of each log to see the timestamps we can see that the all the others got going quickly (initial timestamps are within a second:

    03:20:21 Started by upstream project "[node-test-binary-windows-native-suites](https://ci.nodejs.org/job/node-test-binary-windows-native-suites/)" build number 
    

    but the nodes=win2022-vs2022-COMPILED_BY-vs2022_clang one was queued up for over 2½ hours before it got scheduled

    05:57:54 Started by upstream project "[node-test-binary-windows-native-suites" build number 35689
    
    Current chart of build time trend of recent [node-test-pull-request](https://ci.nodejs.org/job/node-test-pull-request/buildTimeTrend) jobs Image
    Chart of [node-test-commit-windows-fanned](https://ci.nodejs.org/job/node-test-commit-windows-fanned/buildTimeTrend) until things had quiesced Image
    Chart of [node-test-binary-windows-js-suites](https://ci.nodejs.org/job/node-test-binary-windows-js-suites/buildTimeTrend) (slightly later than the other two charts) Image

    Also noting that requiring all of the compile jobs to complete before the tests commence is probably suboptimal from a throughput perspective but it's down to the way this has been implemented in the multiconfiguration plugin which means that, for exampe, the arm cannot get queued start until the x64 build is complete - it's probably not a huge problem overall though. ON a counter point the current system does make it easier to see the full set of test results (arm/x64) from one instance of the test job (albeit without having the js and natives together)

  3. sxa commented on Oct 25, 2026

    @sxa
    MemberAuthor

    Due to the delays which are visible on some of the log snippets above I've disabled (Added a -XXX4537 suffix to the build labels) test-rackspace-win2022_vs2022-x64-6 from being able to run build jobs leaving it dedicated for testing and will keep an eye on the trend over the next week. I think I'd like to do the same on -5 to leave two dedicated for testing (as we do for the other test labels, which would also mean that only four machines need ccaches to be repopulated) but we can leave that for a week or two before trialling - ideally including one cycle of a V8 patch being merged first. Fanned jobs from 81421 (test-commit windows 69946, js-suites 44129) are with this change applied.

  4. sxa commented on Oct 26, 2026

    @sxa
    MemberAuthor

    There was a V8 PR test today at job https://ci.nodejs.org/job/node-test-commit-windows-fanned/81438/ which caused a bit of a backlog but not too severe (Up to 3 hours, with the test jobs occasionally going over an hour but not being as badly delayed as we've seen in the past).

    While it has not fully got back to normal yet there are no windows compile jobs running at the moment so it's close to stable again. However the V8 PR (66820) has now been merged and so each machine will start repopulating its caches as of node-test-pull-request#78674 / windows fanned job 81454

  5. sxa commented on Oct 27, 2026

    @sxa
    MemberAuthor

    Looking at the windows jobs since the two at the end of the last comment:

    • Test jobs (including queuing) only once went over an hour of time (before the changes it could hit three hours)
    • node-compile windows was taking about 3-4 hours for most jobs while it populated the updated cache across the machines (At the time of writing up to build 69982 all except rackspace-2 have rebuilt both x64 and arm64 - that machine seems to have consistently been given the debug and test jobs overnight!) The slowest overall windows fanned job was just over 4h18m which I would say is acceptable for a cache invalidation scenario.

    Another point of note relating to something I said earlier in this issue:

    Also noting that requiring all of the compile jobs to complete before the tests commence is probably suboptimal from a throughput perspective

    For each build the x64 compile jobs are typically faster than the arm ones - around 2-2½ hours vs just over 3½ in the worst case for arm64. This shows that we are artificially delaying the start of x64 testing while the arm64 builds complete due to the way the fanning setup works.

    For a sample job, node-compile-windows #69978 took 3h15. 1h39 for x64 and 3h15 for arm64. This will have meant that initiation of the x64 testing (including scheduling on the now "test only" machine) would have been delayed by about 1½ hours. It's not something that's necessarily critical to fix now, but worthy of consideration for any future redesign of the jobs. Similar for 69982 - 23 minutes on x64, over 4 hours on arm64.

    Some charts which show the spikes from last week from V8 changes and the new ones from this week

    Times mentioned in the text of the twisty are the worst case seen in the period while rebuilding with V8 changes.

    node-test-commit windows fanned (Up to ~6 hrs last week, ~4¼ hrs now) Image
    node-compile-windows (Hit 6-7 hrs last week, ~4hrs this week) Image
    node-test-binary-windows-js-suites (Huge difference from freeing up one machine - ~3hrs peak last week, ~1hr today) Image

    Overall I think we're in a much better position now. I may look at doing another table equivalent to the one I did for the initial analysis but I think the charts say all we need to know for now :-)

  6. sxa commented on Nov 4, 2026

    @sxa
    MemberAuthor

    Noting that over most of the last week it's been particularly bad. There has been v8 updates, some changes with V8 patches being merged and also a few security-release related jobs running. The V8 changes has there has been an unusually high number of cache invalidations plus there is additional load from V8 15.7 PR testing. On a positive the move to having one dedicated test machine which doesn't run any builds does mean there are fewer cases of the higher level fanned job being held up waiting for tests to be scheduled. The five build machines have been in constant operation for the last few days, and while node-tet-commit-windows-fanned has been running at over 10 hours to completion in many cases over the last two days it is coming down, albeit still about 5 hours at the time of writing.

    This is the history of the arm64 axis of node-compile-windows at the time of writing: Image

    Still incredibly spiky:

    Image
    Linux/x64 (RHEL8) history in the last day for comparison Image Image
  7. sxa commented on Nov 5, 2026

    @sxa
    MemberAuthor
    Windows queues have cleared overnight after a fairly terrible few days of long delays Image
    Equivalent chart for node-test-pull-request

    Noting that the "flat" bit of this about 2/3 along the X axis was from a github outage that was causing jobs to fail (Slack thread) with git@github.com: Permission denied (publickey).

    Image

    I do wonder if we could gain a further advantage by either:

    • Splitting the build machines as arm OR x64 e.g. remove the win-vs2022-arm64 and win-vs2022_clang-arm64 labels from one two or three of the five remaining build machines [*]
    • Removing debug builds from the regular list (to reduce contention ont he build machines) and only running them int he daily jobs.

    [*] - I'm suggesting arm64 first here because that platform takes longer to build in the invalidation scenario so have more to gain by building with fewer invalidations) It would also let us more directly compare how arm64 and x64 queue depths behaave if we leave x64 and x64/debug able to build across any of the five machines). Since the arm and x64 builds are almost always initiated together there should be little disadvantage from an overall utilisation perspetive to this as they'd generally both be initiated at the same time anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions