Skip to content

perf(cache): index artifact cache entries by session (Closes #142) - #185

Merged
harsharajkumar-273 merged 1 commit into
harsharajkumar-273:mainfrom
SakethSumanBathini:perf/142-index-artifact-cache-by-session
Aug 4, 2026
Merged

perf(cache): index artifact cache entries by session (Closes #142)#185
harsharajkumar-273 merged 1 commit into
harsharajkumar-273:mainfrom
SakethSumanBathini:perf/142-index-artifact-cache-by-session

Conversation

@SakethSumanBathini

Copy link
Copy Markdown
Contributor

Closes #142

The stated bug can't occur, but the suggested fix is worth making

The issue reports that "if multiple concurrent builds clear or populate cache entries during iteration, map deletion during iteration can cause stale byte count calculations". I don't think that's reachable:

  • Deleting from a Map during for...of is explicitly well-defined — the iterator tolerates it and simply skips removed entries.
  • Node is single-threaded and invalidateSession is synchronous throughout, so no other build can interleave inside that loop. There is no point at which _bytes is observed half-updated.

I ran a property test to check rather than assume: 40,000 randomised set/get/invalidateSession operations against the current implementation, asserting after every one that _bytes equals the sum of live entry sizes.

OLD: 40000 invariant checks, 0 violations | entries=15 bytes=3274383
NEW: 40000 invariant checks, 0 violations | entries=15 bytes=3274383

No drift, and identical final state before and after this change — so this is a refactor with no behavioural difference, not a correctness fix. I've titled it perf rather than fix for that reason.

What the index is actually worth

invalidateSession scanned the whole map to find one session's entries — up to MAX_ARTIFACT_CACHE_ENTRIES (200) comparisons per call. It's called from four places: session cleanup, the cache-expiry sweep, workspace replacement, and build teardown, and it runs whether or not the session ever cached anything.

That last part is the cost. A session that cached nothing still pays a full 200-entry scan:

                                                    OLD        NEW
200k invalidations, cache drains immediately        18ms       23ms
500k invalidations, full cache, session owns none    1287ms     12ms

The second row is the realistic one, and it's ~107× faster. The first row is slower, and I'd rather say so than leave it out — with an empty map the old loop is trivial while the new path still does a Map.get. That difference is ~25ns per call and inverts as soon as the cache holds anything.

Change

A Map<string, Set<string>> from session to owned keys, maintained in three places rather than one — which is the real risk of this change, so each is deliberate:

  • set unindexes against the entry's previous sessionId before reindexing, since the same key can be rewritten under a different session.
  • _evict unindexes the entry it drops. Missing this would leave keys in the index pointing at nothing.
  • _unindex deletes the set once empty, so _bySession doesn't accumulate one entry per session the process has ever seen — which would be a slower leak than the one this replaces.

get is untouched; LRU reordering doesn't change ownership.

The property test above asserts index consistency too — every indexed key resolves to a live entry whose sessionId matches, and the index size equals the map size — across all 40,000 operations, with 0 violations.

Note on ordering

This touches buildExecutor.ts at lines 40-104. My open branches for #104 (exportZip, ~1699) and #107 (cleanup, ~1808) touch the same file well away from here; I'd land this one after those to keep their diffs clean.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@SakethSumanBathini, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7ef666f5-2818-4ac1-adbc-f1fad205781f

📥 Commits

Reviewing files that changed from the base of the PR and between fc357c5 and 4277632.

📒 Files selected for processing (1)
  • backend/src/services/buildExecutor.ts

Comment @coderabbitai help to get the list of available commands.

@harsharajkumar-273 harsharajkumar-273 added ELUSOC Required Tracking ADVENTURER Intermediate (25 pts) labels Aug 4, 2026
@harsharajkumar-273
harsharajkumar-273 merged commit 859f0c5 into harsharajkumar-273:main Aug 4, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ADVENTURER Intermediate (25 pts) ELUSOC Required Tracking

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Performance] In-Memory Build Artifact Cache (ArtifactLRUCache) Eviction Leak Across Parallel Builds

2 participants