Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add caching of node_modules and eslintcache to Github Actions #4

Merged
merged 11 commits into from
Apr 30, 2021

Conversation

mattrunyon
Copy link
Collaborator

Cache all node_modules folders. If any package-lock changes the cache will miss and trigger npm ci. Most of the time this should let us skip npm ci which also calls lerna bootstrap saving ~2min per run.

Also cache the .eslintcache files generated in each package. Currently those will rarely miss, if ever, but cache for each branch will be preferred. This should greatly speed up the eslint Jest test we have in each package. Locally npm run test:ci took ~54s w/o eslintcache and ~30s w/ eslintcache on a rerun without changing files.

@mattrunyon mattrunyon requested a review from mofojed April 27, 2021 22:25
@mattrunyon mattrunyon self-assigned this Apr 27, 2021
@mattrunyon mattrunyon added the enhancement New feature or request label Apr 27, 2021
Comment on lines 60 to 65
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very knowledgeable about this repo or NPM, but shouldn't npm ci be a fast no-op if node_modules is already populated?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't take too long (it actually only installs the dependencies of the root project), but the post install script which uses lerna to bootstrap and link the packages (and install their much larger list of dependencies) is slow (60-120s).

I know we can speed up lerna bootstrap by removing the hoist flag at the cost of space from redundant dependencies being installed in multiple packages. It will still take 45-60s though I think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, using the presence of a cache hit to skip a full step is a bit hacky, and in general pretty dangerous. Is there any sort of lerna cache we could take advantage of? Is there a more appropriate task that we should be running, and we shouldn't need to do post install / lerna stuff?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's a bit hacky. I don't know if it's necessarily that dangerous though since it's matching only if the hash of all package-locks is unchanged. The actual files can still change and be used correctly since lerna symlinks node_modules within the project (e.g. client-ui depends on grid. node_modules/@deephaven/grid is symlinked to packages/grid). If package-lock hasn't changed then that indicates the exact dependencies are the same as they were before.

Using yarn might fix part of the speed issue, but I was having other problems with yarn locally with the license files being symlinks (it couldn't find the file).

The vast majority of our PRs (90%+, probably higher) don't touch package-lock files. If we can speed up lerna + npm then this wouldn't be necessary, but npm is painfully slow when doing a no-op install.

Also I think npm ci actually deletes node_modules to start. Which it should only be deleting the small set of dependencies from the root. That on its own takes ~10 seconds for me locally when I remove the postinstall script.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devinrsmith why is using the cache hit to skip a step hacky? That's exactly what this action is for: https://github.com/actions/cache#skipping-steps-based-on-cache-hit

@mattrunyon There's no more sym linked license files so you should be good from there to try yarn again. I think npm ci should be clearing all the node_modules first, to make sure it has a false pass when a package is removed or something.

package.json Outdated
@@ -10,7 +10,7 @@
"clean:build": "lerna run clean --stream",
"clean:modules": "lerna clean --yes",
"clean": "npm run clean:build && npm run clean:modules",
"bootstrap": "lerna bootstrap --hoist",
"bootstrap": "lerna bootstrap",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to hoist, or we get two versions of react when building.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ya forgot that can cause dev issues. I was just testing it to see what the runtime was without it.

lerna/lerna#2605

Another solution someone had is to install react only at the root. Then you don't need hoist, but I also like hoist for reducing duplicate node modules.

It's a weird annoyance because when the components would be consumed outside the monorepo, these problems won't exist. I really wish lerna or yarn workspaces had a solution for not linking peer/dev deps in some way.

I also saw something about using a webpack alias but I don't think we can do that with CRA. I think the gist of it is in webpack you can tell it explicitly how to resolve react in the app build.

Comment on lines 60 to 65
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devinrsmith why is using the cache hit to skip a step hacky? That's exactly what this action is for: https://github.com/actions/cache#skipping-steps-based-on-cache-hit

@mattrunyon There's no more sym linked license files so you should be good from there to try yarn again. I think npm ci should be clearing all the node_modules first, to make sure it has a false pass when a package is removed or something.

Comment on lines 28 to 35
- name: Cache node_modules
id: cache-node-modules
uses: actions/cache@v2
with:
# If no package-locks or the main package.json have changed, it should be safe to restore all node_modules as they were
# Changing the main package.json may change the postinstall scripts, so it should be unchanged to cache install results
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json', 'package.json') }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectively doing that, except since I'm skipping npm ci on cache hit I'm also hashing the main package.json file.

If we were to change the lerna flags on install/bootstrap and we weren't hashing that file, then it would be a cache hit and skip the install with different flags.

path: |
node_modules
packages/*/node_modules
key: ${{ runner.os }}-modules-${{ hashFiles('package.json', 'package-lock.json', 'packages/*/package-lock.json') }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should include packages/*/package.json as well. In the case where someone accidentally updates a package.json but doesn't update the associated package-lock.json, we don't want to pull from the cache

If dependencies in the package lock do not match those in package.json, npm ci will exit with an error, instead of updating the package lock.

@mattrunyon mattrunyon merged commit aec4aff into main Apr 30, 2021
@mattrunyon mattrunyon deleted the mattrunyon_actions branch April 30, 2021 20:26
mofojed pushed a commit that referenced this pull request Aug 25, 2023
fixes #1439 - Heap usage code now guarantees that new requests won't be
scheduled until all previous requests are complete.

## Testing
I tested by shutting my Mac for ~2 hours and returning. Timings looks
something like this:

### Summary
<img width="310" alt="image"
src="https://github.com/deephaven/web-client-ui/assets/1900643/0ad2d426-2fed-48e8-a255-989ee5d524f3">

It seems that requests still happen occasionally while sleeping. On Wake
up, there were a few ticks resulting in "Unable to get heap usage"
console errors. Then a prompt showed up indicating credentials had
expired at which point requests start succeeding again.

### Full Console Log
```
useAsyncInterval.ts:37 [useAsyncInterval] tick #1. 10068 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 9918
useAsyncInterval.ts:37 [useAsyncInterval] tick #2. 9936 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #3. 10026 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 9958
useAsyncInterval.ts:62 [useAsyncInterval] Setting interval minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #1. 941977 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #2. 13 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #3. 2038465 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #4. 19 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #5. 537820 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #6. 16 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #7. 191633 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #8. 18 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #9. 23720 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #10. 18 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #11. 886367 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #12. 17 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #13. 1010951 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #14. 6 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #15. 650011 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #16. 13 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #17. 731687 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #18. 19 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #19. 120545 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #20. 16 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #21. 446345 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 0
useAsyncInterval.ts:37 [useAsyncInterval] tick #22. 13 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #23. 10999 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 8987
useAsyncInterval.ts:37 [useAsyncInterval] tick #24. 9020 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #25. 10030 ms elapsed since last tick.
HeapUsage.tsx:68 [HeapUsage] Unable to get heap usage Error: Authentication details invalid
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 9955
useAsyncInterval.ts:37 [useAsyncInterval] tick #26. 9979 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #27. 10027 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 9956
useAsyncInterval.ts:62 [useAsyncInterval] Setting interval minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #1. 10695 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 9290
useAsyncInterval.ts:37 [useAsyncInterval] tick #2. 9311 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 10000
useAsyncInterval.ts:37 [useAsyncInterval] tick #3. 10017 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 9969
useAsyncInterval.ts:37 [useAsyncInterval] tick #4. 9992 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 9992
useAsyncInterval.ts:37 [useAsyncInterval] tick #5. 10680 ms elapsed since last tick.
useAsyncInterval.ts:56 [useAsyncInterval] adjusted minIntervalMs: 9308
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants