[No QA] Prevent sticky disks from growing unboundedly - #98422
Conversation
|
@gijoe0295 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b01a28887a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| with: | ||
| key: ${{ format('{0}-{1}-{2}-node-modules-{3}', github.repository, runner.os, runner.arch, inputs.IS_HYBRID_BUILD == 'true' && hashFiles('normalized-package-lock.json', 'patches/**', 'Mobile-Expensify/patches/**') || hashFiles('normalized-package-lock.json', 'patches/**')) }} | ||
| path: node_modules | ||
| commit: ${{ inputs.SEED_STICKY_DISKS == 'true' && 'if-missing' || 'false' }} |
There was a problem hiding this comment.
Rotate keys or replace existing snapshots
The pinned stickydisk action defines if-missing as committing only when no prior snapshot existed, so any already-bloated node_modules disk for the unchanged key will be mounted, treated as a hit via .stickydisk-installed, and then skipped by the seed instead of being replaced. Because regular consumers now use commit: false, those 30–100GB snapshots remain active as long as CI keeps mounting them; use a new key/version or a one-time delete/reseed path so existing contaminated disks are actually replaced.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Yep, we need to remove all the previous sticky disks using the Blacksmith panel when this PR hits main, since these disks are bloated and some of them reach 80 GB
сс: @mountiny
There was a problem hiding this comment.
Ok I can do that when we are ready
|
@mountiny all comments are resolved. Could you please take a look? 🙂 |
|
@MelvinBot review |
Review — looks solid ✅The core mechanism is right: separating producer ( 1. PR body no longer matches the workflow triggerThe description says the seed "runs on every pull request and every push to 2. Dependency-bump PRs lose the cache for their whole lifetimeBecause the seed only runs on 3.
|
|
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / Safari |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.57-0 🚀
|
|
🚀 Deployed to production by https://github.com/Beamanator in version: 9.4.57-3 🚀
Bundle Size Analysis (Sentry): |
Re-apply changes from #98422, which were lost when the fork's main was squashed.

Explanation of Change
The problem
A Blacksmith sticky disk is not a tarball of files like
actions/cache- it is an ext4 block device stored in Blacksmith's Ceph cluster. When a job mounts one, the last committed snapshot is cloned and mounted at the given path, when the job finishes, the clone is committed back and becomes the new snapshot.The important part is when that commit happens: the sticky disk action commits in its post-job phase, so the snapshot always captures the state of the mounted path at the very end of the job. There is no API to commit at the end of a specific step - we cannot say "snapshot
node_modulesright afternpm ciand ignore everything written afterwards". Everything a job writes into the mount before it ends is part of the snapshot.That matters because plenty of tooling writes into
node_modulesduring a job:node_modules/.cache.node_modules/<lib>/android/build,.cxxobject files per ABI, generated codegen sources.Because every job committed (
commitdefaults totrue), each run layered its own generated files into the snapshot, and the next run started from that fatter snapshot and added more. It compounds, and on the block level it is worse than the file listing suggests: deleting or rewriting a file inside the mount frees ext4 blocks, but the freed blocks are never returned to the storage layer -fstrimon a sticky disk mount fails withthe discard operation is not supported. The billed size therefore tracks every block ever written, not the blocks currently in use.The result:
node_modulesthat is 3.8 GB after a cleannpm cigrew into disks reporting 30 GB of live files and 103 GB of allocated blocks in the Blacksmith dashboard after ~3 weeks on the same key. Inspecting one of those disks showed exactly the expected culprits -react-native-reanimatedat 4.0 GB,react-native-workletsat 3.2 GB,react-native-quick-cryptoat 2.9 GB, and so on for every library with native code, against ~50 MB each when freshly installed. Sticky disk storage is billed at $0.50/GB/month, so a single such key cost ~$50/month on its own.The fix
Separate "who produces the snapshot" from "who consumes it".
setupNodetakes a newSEED_STICKY_DISKSinput that controls thecommitmode of thenode_modulesdisks:seedStickyDisks.ymlworkflow passesSEED_STICKY_DISKS: 'true'and getscommit: if-missing. It installs dependencies and does nothing else, so the snapshot it writes is a cleannpm ciresult by construction. It runs on every push tomain, once a key has a snapshot, the seed is a no-op that mounts it and exits.commit: false. Jobs still readnode_modulesfrom the snapshot and may write whatever they like into their own clone - caches, Gradle output,.cxxand all of it is discarded when the clone is unmounted instead of being committed. Disks stay at the size of a clean install permanently.~/.npmdisk keeps committing, withcommit: on-change. Its key has no hash in it, so it has to keep absorbing new package tarballs across lockfile bumps, and it does not suffer the same growth: tarballs are content-addressed, it sits at ~6 GB, andon-changeskips the commit on the majority of runs that never touch it.Cache-hit detection is unchanged: the
node_modules/.stickydisk-installedmarker is written at the end ofsetupNode, so it lands in the seed's snapshot and consumers skipnpm ciwhen they see it.Fixed Issues
$ #98593
$ #98563
Tests
No QA
Offline tests
N/A
QA Steps
No QA
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.