berry: [Bug] Extremely slow `yarn run xxxx` commands on large monorepo

The bug was explained there by my colleague @etiennedupont , but maybe it should be reported here as it concerns v2 ?

https://github.com/yarnpkg/yarn/issues/7917

Bug description

When running scripts defined in package.json using yarn run xxx on a large monorepo, there is an extremely long delay before the command is actually run. This delay is always the same and seems to depend on the size of the monorepo.

Command

Here is a simple example:

In package.json

{
  "scripts": {
    "echo": "echo ok"
  }
}

Then in shell run:

> yarn echo

.... WAITS FOR 265s .....

ok

Using time to confirm the duration:

> time yarn echo
ok
yarn echo  264.68s user 1.33s system 99% cpu 4:26.01 total

What is the current behavior?

Yarn does something using 100% of a CPU core for 265s (on my repo and machine) before actually running the command.

What is the expected behavior?

Yarn runs the command instantly

Steps to Reproduce

  • Have a large monorepo with 176packages in the workspace.
  • yarn install
  • Run any yarn run xxxx command in any of the packages folder.
  • Environment

Node Version: 10.15.3 Yarn v1 Version: 2.0.0-rc.29.git.20200218.926781b7 OS and version: MacOS Catalina

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 38 (31 by maintainers)

Most upvoted comments

To give you some context, the way the resolvePeerDependency function works, it traverses the tree to figure out for each node N what are the dependencies it needs to extract from its parent P to satisfy the peer dependencies of N - then once it’s done, it recurses inside the newly created package N2 (which doesn’t have peer dependencies anymore) in order to let its transitive dependencies inherit their peer dependencies as well.

I’m not entirely sure of the circumstances under which you’d end up with a huge tree - there’s likely a mix of peer dependencies and regular dependencies that trigger a catastrophic expansion. My main guess is that something somewhere (possibly in multiple places) is listed as a regular dependency instead of being a peer dependency, causing Yarn to dig into it and generating those huge graphs. If you print parentLocator.name in your console.group you might get a clue by seeing which package is referenced the most.

Hey @RaynalHugo! I took a quick look at generating a huge monorepo with 200 packages, but no luck - my run time was mostly unaffected (I used the following script inside a project configured with a packages/* glob pattern:).

for x in {1..200} ; do
    (mkdir -p packages/pkg-$x && cd packages/pkg-$x && yarn init)
done

I think the best would be to use --no-minify as you guessed, and put some timing statements around setupWorkspaces and resolveEverything. Depending on the results other places may be good profiling targets as well, such as initializePackageEnvironments.

Closing with #997 and #998

I didn’t know humans were supposed to review yarn.lock files aha, ours is 45,000 lines.

For internal lockfiles it’s not so much required, but for open-source ones it’s important to make sure the resolutions point to the expected location (otherwise I could make a PR adding lodash, except that the lockfile would actually point to my-malicious-package).

Is it something you would work on soon, what would be the timeline without action on our part?

I think I need to work on the algorithm performances first, because it’s less likely someone else will be able to efficiently navigate this part of the code. Given my other wips, I’d say at least a few weeks before I can implement it - so any help would be much appreciated! 😃

Otherwise, do you think this could be implemented as a yarn plugin?

It would probably be better to implement this in the core - it’s unlikely someone would want a different implementation, so a hook just for that would be awkwardly specific. Plus the experience won’t be worse because of it, so better ship it by default.

What would be the complexity of this development? I have a feeling it could be done with a few well-placed lines of code that we could work on on our side.

I don’t think it would be very complex - basically what I think would need to be done on the top of my head:

  • The persistLockfile function would have to be extended to generate the virtual state file as well as the lockfile.

  • A new function (hydrateVirtualPackages?) would have to be added to the Project instance - it would load the data from the virtual state file (and potentially fallback on the regular resolution if the file isn’t found? I’m not certain about this 🤔). The implementation would look like setupResolutions, except that we would reuse the existing originalPackages as much as possible (cf the shortened format I mentioned).

  • The yarn run function (and potentially others, you can find them by grepping lockfileOnly: true) would need to be modified to reference hydrateVirtualPackages instead of calling the resolver manually.

  • The documentation would have to be updated to reference this in the section where we detail the gitignore patterns (the virtual state would have to be ignored when not using zero installs).

@arcanis regarding the .yarn/virtual-state.yml idea, you got us excited! So here are a few additional questions:

  • Is it something you would work on soon, what would be the timeline without action on our part?
  • Otherwise, do you think this could be implemented as a yarn plugin?
  • What would be the complexity of this development? I have a feeling it could be done with a few well-placed lines of code that we could work on on our side.

Best

I didn’t know humans were supposed to review yarn.lock files aha, ours is 45,000 lines.

Indeed, I think yarn v2 is actually the first-ever fully-correct package manager and this is a big deal and much appreciated.

The solution of having .yarn/virtual-state.yml looks very good to me, and as I said it would totally solve our problem. Also, it might actually be the only way to fully solve this problem: I am not sure that optimizing the resolution pass algorithm would do the job. Right now it can take up to 400s, even making it 100 times faster would still be considered quite slow, taking 4s to run a script that sometimes completes in 2s.

So conclusion: I give a big 👍 to the .yarn/virtual-state.yml , it seems to be the last show stopper for us to adopt yarn v2 in production since everything now works as expected.

We did that and ran:

node .yarn/releases/yarn-sources.js echo > parentLocators.txt
sort parentLocators.txt | uniq -c | sort

It gives these stats: the most imported package is @babel/helper-plugin-utils, which is imported… 39163 times… I guess this is a lot even for a monorepo of 160 packages.

 360 parentLocator: @jest/types@24.9.0
 361 parentLocator: @null/schema-utils@1.0.0
 364 parentLocator: @babel/plugin-syntax-dynamic-import@7.2.0
 364 parentLocator: @storybook/node-logger@6.0.0-alpha.8
 365 parentLocator: @null/copy-to-clipboard@3.2.1
 366 parentLocator: @null/find-cache-dir@3.2.0
 368 parentLocator: @null/micromatch@4.0.2
 376 parentLocator: @null/fs-extra@8.1.0
 381 parentLocator: @babel/helper-builder-react-jsx@7.8.3
 381 parentLocator: @babel/plugin-transform-react-jsx-self@7.8.3
 381 parentLocator: @babel/plugin-transform-react-jsx-source@7.8.3
 381 parentLocator: @babel/plugin-transform-react-jsx@7.8.3
 381 parentLocator: @null/mime@2.4.4
 382 parentLocator: @babel/plugin-transform-react-display-name@7.8.3
 384 parentLocator: @null/node-fetch@2.6.0
 396 parentLocator: @null/glob@7.1.6
 414 parentLocator: @null/resolve@1.10.0
 419 parentLocator: @null/uuid@3.3.2
 466 parentLocator: @babel/plugin-proposal-object-rest-spread@7.8.3
 490 parentLocator: @null/loose-envify@1.4.0
 514 parentLocator: @babel/plugin-syntax-typescript@7.8.3
 514 parentLocator: @babel/plugin-transform-typescript@7.8.3
 542 parentLocator: @null/commander@2.20.0
 544 parentLocator: @null/focus-lock@0.6.6
 544 parentLocator: @null/lowlight@1.11.0
 544 parentLocator: @null/react-clientside-effect@1.2.2
 544 parentLocator: @null/react-focus-lock@2.2.1
 544 parentLocator: @null/react-popper-tooltip@2.10.1
 544 parentLocator: @null/react-syntax-highlighter@11.0.2
 544 parentLocator: @null/react-textarea-autosize@7.1.2
 544 parentLocator: @null/use-callback-ref@1.2.1
 544 parentLocator: @null/use-sidecar@1.0.2
 544 parentLocator: @storybook/client-api@6.0.0-alpha.8
 544 parentLocator: @storybook/components@6.0.0-alpha.8
 544 parentLocator: @types/react-textarea-autosize@4.3.5
 545 parentLocator: @null/eventemitter3@4.0.0
 545 parentLocator: @null/highlight.js@9.13.1
 545 parentLocator: @null/prismjs@1.19.0
 545 parentLocator: @null/refractor@2.10.1
 545 parentLocator: @null/stable@0.1.8
 546 parentLocator: @null/detect-node@2.0.4
 547 parentLocator: @null/fast-json-stable-stringify@2.1.0
 547 parentLocator: @null/is-plain-object@3.0.0
 549 parentLocator: @null/simplebar-react@1.0.0
 554 parentLocator: @null/simplebar@4.0.0
 555 parentLocator: @null/react-popper@1.3.7
 555 parentLocator: @null/typed-styles@0.0.7
 558 parentLocator: @null/deep-equal@1.1.1
 562 parentLocator: @babel/compat-data@7.8.5
 562 parentLocator: @null/levenary@1.1.1
 563 parentLocator: @babel/plugin-syntax-nullish-coalescing-operator@7.8.3
 565 parentLocator: @babel/plugin-syntax-optional-chaining@7.8.3
 575 parentLocator: @null/browserslist@4.8.6
 591 parentLocator: @null/resolve@1.15.1
 593 parentLocator: @null/rollup-pluginutils@2.8.2
 598 parentLocator: @null/webpack@4.41.6
 607 parentLocator: @null/json5@2.1.1
 610 parentLocator: @babel/plugin-proposal-dynamic-import@7.8.3
 610 parentLocator: @babel/plugin-transform-modules-commonjs@7.8.3
 611 parentLocator: @babel/plugin-transform-async-to-generator@7.8.3
 613 parentLocator: @babel/helper-builder-binary-assignment-operator-visitor@7.8.3
 613 parentLocator: @babel/plugin-proposal-optional-catch-binding@7.8.3
 613 parentLocator: @babel/plugin-proposal-unicode-property-regex@7.8.3
 613 parentLocator: @babel/plugin-transform-arrow-functions@7.8.3
 613 parentLocator: @babel/plugin-transform-block-scoped-functions@7.8.3
 613 parentLocator: @babel/plugin-transform-block-scoping@7.8.3
 613 parentLocator: @babel/plugin-transform-classes@7.8.3
 613 parentLocator: @babel/plugin-transform-computed-properties@7.8.3
 613 parentLocator: @babel/plugin-transform-dotall-regex@7.8.3
 613 parentLocator: @babel/plugin-transform-duplicate-keys@7.8.3
 613 parentLocator: @babel/plugin-transform-exponentiation-operator@7.8.3
 613 parentLocator: @babel/plugin-transform-for-of@7.8.4
 613 parentLocator: @babel/plugin-transform-function-name@7.8.3
 613 parentLocator: @babel/plugin-transform-literals@7.8.3
 613 parentLocator: @babel/plugin-transform-member-expression-literals@7.8.3
 613 parentLocator: @babel/plugin-transform-modules-amd@7.8.3
 613 parentLocator: @babel/plugin-transform-modules-systemjs@7.8.3
 613 parentLocator: @babel/plugin-transform-modules-umd@7.8.3
 613 parentLocator: @babel/plugin-transform-named-capturing-groups-regex@7.8.3
 613 parentLocator: @babel/plugin-transform-new-target@7.8.3
 613 parentLocator: @babel/plugin-transform-object-super@7.8.3
 613 parentLocator: @babel/plugin-transform-parameters@7.8.4
 613 parentLocator: @babel/plugin-transform-property-literals@7.8.3
 613 parentLocator: @babel/plugin-transform-regenerator@7.8.3
 613 parentLocator: @babel/plugin-transform-reserved-words@7.8.3
 613 parentLocator: @babel/plugin-transform-shorthand-properties@7.8.3
 613 parentLocator: @babel/plugin-transform-spread@7.8.3
 613 parentLocator: @babel/plugin-transform-sticky-regex@7.8.3
 613 parentLocator: @babel/plugin-transform-template-literals@7.8.3
 613 parentLocator: @babel/plugin-transform-typeof-symbol@7.8.4
 613 parentLocator: @babel/plugin-transform-unicode-regex@7.8.3
 613 parentLocator: @null/core-js-compat@3.6.4
 613 parentLocator: @null/regenerator-transform@0.14.1
 614 parentLocator: @babel/helper-define-map@7.8.3
 614 parentLocator: @babel/helper-hoist-variables@7.8.3
 615 parentLocator: @babel/plugin-transform-destructuring@7.8.3
 616 parentLocator: @null/globals@11.12.0
 619 parentLocator: @babel/helper-call-delegate@7.8.3
 620 parentLocator: @babel/helper-get-function-arity@7.8.3
 623 parentLocator: @babel/helper-simple-access@7.8.3
 637 parentLocator: @types/webpack-env@1.15.1
 652 parentLocator: @babel/types@7.8.3
 657 parentLocator: @babel/plugin-proposal-object-rest-spread@7.6.2
 679 parentLocator: @null/slash@2.0.0
 726 parentLocator: @null/markdown-to-jsx@6.11.0
 726 parentLocator: @null/react-helmet-async@1.0.4
 726 parentLocator: @storybook/channel-postmessage@6.0.0-alpha.8
 727 parentLocator: @null/unquote@1.1.1
 733 parentLocator: @null/react-fast-compare@2.0.4
 747 parentLocator: @null/regenerator-runtime@0.13.3
 873 parentLocator: @null/chalk@2.4.2
 923 parentLocator: @null/shallowequal@1.1.0
 926 parentLocator: @null/schema-utils@2.6.4
1020 parentLocator: @null/tslib@1.10.0
1033 parentLocator: @babel/helper-create-class-features-plugin@7.8.3
1034 parentLocator: @babel/helper-member-expression-to-functions@7.8.3
1073 parentLocator: @babel/plugin-syntax-dynamic-import@7.8.3
1101 parentLocator: @null/popper.js@1.16.1
1143 parentLocator: @babel/plugin-syntax-jsx@7.8.3
1166 parentLocator: @null/mkdirp@0.5.1
1226 parentLocator: @babel/plugin-syntax-optional-catch-binding@7.8.3
1227 parentLocator: @babel/plugin-syntax-json-strings@7.8.3
1228 parentLocator: @babel/plugin-syntax-async-generators@7.8.4
1230 parentLocator: @babel/helper-remap-async-to-generator@7.8.3
1304 parentLocator: @babel/runtime@7.8.4
1359 parentLocator: @null/semver@5.7.1
1494 parentLocator: @babel/helper-module-imports@7.8.3
1512 parentLocator: @babel/helper-annotate-as-pure@7.8.3
1633 parentLocator: @storybook/addons@6.0.0-alpha.8
1648 parentLocator: @babel/helper-optimise-call-expression@7.8.3
1649 parentLocator: @babel/helper-split-export-declaration@7.8.3
1844 parentLocator: @null/babel-plugin-dynamic-import-node@2.3.0
1909 parentLocator: @null/loader-utils@1.4.0
2070 parentLocator: @babel/plugin-syntax-object-rest-spread@7.8.3
2177 parentLocator: @storybook/api@6.0.0-alpha.8
2190 parentLocator: @null/shallow-equal@1.2.1
2260 parentLocator: @babel/helper-replace-supers@7.8.3
2263 parentLocator: @babel/helper-function-name@7.8.3
2359 parentLocator: @null/store2@2.10.0
2360 parentLocator: @null/telejson@3.3.0
2452 parentLocator: @babel/helper-create-regexp-features-plugin@7.8.3
2452 parentLocator: @null/regexpu-core@4.6.0
2461 parentLocator: @babel/helper-module-transforms@7.8.3
2641 parentLocator: @null/react@16.8.6
2722 parentLocator: @null/fast-deep-equal@3.1.1
2722 parentLocator: @storybook/router@6.0.0-alpha.8
2928 parentLocator: @null/semver@6.0.0
3065 parentLocator: @babel/helper-regex@7.8.3
3538 parentLocator: @null/deep-object-diff@1.1.0
3538 parentLocator: @storybook/theming@6.0.0-alpha.8
3624 parentLocator: @emotion/styled@10.0.27
3626 parentLocator: @null/babel-plugin-emotion@10.0.27
3709 parentLocator: @emotion/styled-base@10.0.27
3720 parentLocator: @null/emotion-theming@10.0.27
3721 parentLocator: @emotion/weak-memoize@0.2.5
3829 parentLocator: @null/hoist-non-react-statics@3.3.2
3905 parentLocator: @null/resolve-from@5.0.0
3998 parentLocator: @null/qs@6.9.1
4000 parentLocator: @emotion/core@10.0.27
4002 parentLocator: @emotion/sheet@0.9.4
4193 parentLocator: @emotion/css@10.0.27
4194 parentLocator: @emotion/cache@10.0.27
4445 parentLocator: @null/polished@3.4.4
4628 parentLocator: @storybook/channels@6.0.0-alpha.8
4899 parentLocator: @types/reach__router@1.3.0
4901 parentLocator: @reach/router@1.3.1
5080 parentLocator: @null/ts-dedent@1.1.1
5261 parentLocator: @storybook/core-events@6.0.0-alpha.8
5325 parentLocator: @null/react-lifecycles-compat@3.0.4
5456 parentLocator: @null/create-react-context@0.3.0
5479 parentLocator: @null/gud@1.0.0
5898 parentLocator: @storybook/csf@0.0.1
6013 parentLocator: @null/warning@4.0.3
6757 parentLocator: @null/invariant@2.2.4
7247 parentLocator: @emotion/is-prop-valid@0.8.6
7445 parentLocator: @null/util-deprecate@1.0.2
7713 parentLocator: @emotion/serialize@0.11.15
7713 parentLocator: @emotion/utils@0.11.3
8399 parentLocator: @null/lodash@4.17.15
8982 parentLocator: @storybook/client-logger@6.0.0-alpha.8
9710 parentLocator: @null/memoizerific@1.11.3
12164 parentLocator: @null/global@4.4.0
12385 parentLocator: @null/core-js@3.6.4
14656 parentLocator: @babel/runtime@7.6.3
17378 parentLocator: @null/prop-types@15.7.2
39163 parentLocator: @babel/helper-plugin-utils@7.8.3

We did the group thing, the stacks don’t seem that deep, but the stream is neverending, there are millions of lines…

          resolvePeerDependencies
        resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
    resolvePeerDependencies
      resolvePeerDependencies
      resolvePeerDependencies
      resolvePeerDependencies
      resolvePeerDependencies
      resolvePeerDependencies
      resolvePeerDependencies
      resolvePeerDependencies
      resolvePeerDependencies
      resolvePeerDependencies
      resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
          resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
                resolvePeerDependencies
                resolvePeerDependencies
          resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
            resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
                resolvePeerDependencies
                resolvePeerDependencies
                resolvePeerDependencies
                resolvePeerDependencies
            resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
              resolvePeerDependencies
      resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
        resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
          resolvePeerDependencies
            resolvePeerDependencies
            resolvePeerDependencies
    resolvePeerDependencies
      resolvePeerDependencies
... continues for 10 minutes...
Screenshot 2020-02-20 at 15 44 21