[rush] rushd: every warm request re-fingerprints the whole workspace (sync file walk, ajv schema recompiles, sync `ps` spawns on the daemon event loop)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 45/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- nodejs, typescript
- 領域
- performance, tooling
調査の方向性
Start by examining the files mentioned: WorkspaceInputFingerprint.ts (lines 62-99, 221-241) for the synchronous file walk and hashing, and LockFile for the ps spawn. Look at how WorkspaceRuntimeFingerprintCache is used and where ajv schema compilation occurs (JsonSchema.ensureCompiled). Profile the daemon with --cpu-prof to see the bottlenecks in action. The fix involves memoizing file stats, caching compiled schemas, and replacing synchronous calls with async or procfs reads. Testing requires a multi-project rush workspace to measure warm request latency before and after changes.
索引モデルが issue の本文から書いたものです。
説明
Summary
Most of the daemon-side time for every warm request (tier-0 reuse path) goes to re-fingerprinting the whole workspace from scratch, before anything is scheduled. The cost grows with repo size, and part of the work is synchronous, so it blocks the event loop that serves all clients. After client startup (#6054) is fixed, this becomes the dominant fixed cost of a warm daemon request.
| workspace | captureWorkspaceInputFingerprintAsync (warm median) |
captureProjectConfigurationFingerprintAsync (median) |
sync runtime _hashPaths |
|---|---|---|---|
| 30-project synthetic | 61 ms | 22 ms | 22-27 ms |
| rushstack (196 projects) | 263 ms | 372 ms | 23 ms |
Fully warm no-op latency vs graph size (synthetic): 1.26 s at 1 project, 2.84 s at 50, 8.67 s at 300. That is about 25 ms per project, bound by re-validation. A daemon CPU profile over 12 warm requests in a 30-project workspace shows:
- lstat 466 ms and realpath 263 ms (from
_hashPaths/hashFilesAsync); - ajv schema compilation 530 ms (
JsonSchema.ensureCompiledvia heft-config-file: uncached project config loads recompile the schemas); LockFile.tryAcquire -> getProcessStartTime -> spawnSync('ps')353 ms, about 30-54 ms per call. It runs on every request (acquireExecutionLeaseAsync), in warm-set maintenance, and in#prepareAsync.
As a result, the daemon event loop stalls for up to 190-260 ms (p99) while preparing warm requests, delaying every other client.
Repro steps
Call the same rush-lib functions that WorkspaceRequestLifecycle calls on the tier-0 path (#captureAsync at WorkspaceRequestLifecycle.ts:373 and captureProjectConfigurationFingerprintAsync at :384), with a persistent WorkspaceRuntimeFingerprintCache, 10 times against a 30-project and a 196-project workspace. Or profile the daemon (--cpu-prof) during repeated warm no-op rush-client build requests.
Expected result: Warm requests do incremental work proportional to what changed: stat-identity memoization of definition files, cached compiled schemas, and cached runtime fingerprints. Nothing synchronous or subprocess-based sits on the daemon event loop per request.
Actual result: Full recomputation on every request, with a synchronous file walk, schema recompiles, and a synchronous ps spawn.
Details
Root cause (main @ 60007c9a8c):
libraries/rush-lib/src/api/WorkspaceInputFingerprint.ts:62-99(_hashPaths): a synchronous recursive readdir plusstatSync/realpathSyncof the ~500+ runtime files on every capture.:221-241(hashFilesAsync): re-reads and re-hashes every definition file (rush.json, common/config/**, 4 files per project) with concurrency 3, with no stat memo.:199-215(captureProjectConfigurationFingerprintAsync): uncached project configuration loads recompile ajv schemas.LockFilegetProcessStartTimespawnspssynchronously. On Linux, read/proc/<pid>/statinstead.
Suggested fix: memoize by file identity (dev/ino/size/mtime/ctime), making hashes a cheap stat check; cache compiled schemas process-wide; compute the runtime fingerprint once per process, since runtime files cannot change without a restart; replace the synchronous ps with procfs; and move the remaining synchronous I/O off the event loop.
This was found during an automated performance/behavior analysis of rush-client/rushd on Linux and independently confirmed.
Standard questions
| Question | Answer |
|---|---|
@microsoft/rush globally installed version? |
built from main @ 60007c9a8c (5.179.0) |
rushVersion from rush.json? |
5.179.0 |
pnpmVersion, npmVersion, or yarnVersion from rush.json? |
[email protected] |
(if pnpm) useWorkspaces from pnpm-config.json? |
true |
| Operating system? | Linux (WSL2 Ubuntu 24.04) |
| Would you consider contributing a PR? | Yes |
Node.js version (node -v)? |
22.23.2 |
- 主要言語
- TypeScript
- スター
- 6.5k
- フォーク
- 708
- 平均マージ
- 4日 13時間
- マージ済み PR(30日)
- 62
環境構築
このプロジェクトの環境構築ファイルはまだ確認していません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
microsoft/rushstack のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
microsoft/rushstack#5971 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
microsoft/rushstack#5902 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
microsoft/rushstack#5839 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
microsoft/rushstack#5683 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
メンテナーはふだん 1 日以内に返信
microsoft/rushstack の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
diegosouzapw/OmniRoute#14869 ·
メンテナーはふだん 1 日以内に返信
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 94/100
メンテナーはふだん 1 日以内に返信
-
status: waiting triage
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
freeCodeCamp/freeCodeCamp#70412 ·
メンテナーはふだん 1 日以内に返信
-
Mend: dependency security vulnerability untriaged
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
opensearch-project/OpenSearch-Dashboards#12816 ·
メンテナーはふだん 1 日以内に返信