Skip to content

perf(controller): eliminate redundant treehash storage reads - #306

Open
Arjunmehta312 wants to merge 2 commits into
uber:mainfrom
Arjunmehta312:perf/reuse-treehash-in-get-changed-targets
Open

perf(controller): eliminate redundant treehash storage reads#306
Arjunmehta312 wants to merge 2 commits into
uber:mainfrom
Arjunmehta312:perf/reuse-treehash-in-get-changed-targets

Conversation

@Arjunmehta312

Copy link
Copy Markdown

Summary

Avoid rereading treehash values during GetChangedTargets by reading them
once and reusing them across cache lookup, graph lookup, and background
cache population.

Validation

  • Treehash storage reads reduced from 6 to 2 in the affected cache-miss
    path.
  • Total storage reads reduced from 9 to 5 in the regression scenario.
  • Added a regression test covering the redundant-read case.
  • make test passes.
  • make lint passes.

GetChangedTargets previously read the same treehash values multiple times
during cache lookup and background cache population.

Read the treehashes once and pass the values through the existing call
chain so they can be reused.

This reduces treehash storage.Get calls from 6 to 2 in the affected cache
miss path.
@Arjunmehta312
Arjunmehta312 requested review from a team as code owners August 27, 2026 14:27
@CLAassistant

CLAassistant commented Aug 27, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@xytan0056 xytan0056 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

.

@xytan0056 xytan0056 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for contributing. good catch

// letting an orchestrator see it could poison the shared cache with
// stripped graphs.
func (c *controller) getGraph(ctx context.Context, e *metrics.Emitter, req entity.GetTargetGraphRequest) (storage.GraphReader, error) {
// If treehash is provided (non-empty), it is used instead of reading from storage.

@xytan0056 xytan0056 Aug 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

threading "" as "not provided" through 4 function signatures is fragile — it conflates "caller didn't look it up" with "looked up, doesn't exist in storage."

Consider a separate resolver with memoization instead :

type treehashResolver struct {
    storage storage.Storage
    emitter *metrics.Emitter
    op      string
    mu      sync.Mutex
    cache   map[string]resolvedTreehash
}

type resolvedTreehash struct {
    value string
    err   error
}

func (r *treehashResolver) resolve(ctx context.Context, build entity.BuildDescription) (string, error) {
    key := cachekey.GetTreehashCachePath(build)
    r.mu.Lock()
    if entry, ok := r.cache[key]; ok {
        r.mu.Unlock()
        return entry.value, entry.err
    }
    r.mu.Unlock()
    value, err := readTreehash(ctx, r.storage, build, r.emitter, r.op)
    r.mu.Lock()
    r.cache[key] = resolvedTreehash{value: value, err: err}
    r.mu.Unlock()
    return value, err
}

Create it at the top of GetChangedTargets and let serveChangedTargetsFromCache, getGraph, and cacheComparedTargets call resolver.resolve() directly. Second call for the same build is a map hit , no "" sentinel, no conditional branches, and if a new code path needs the treehash later it just calls resolve() for free.

@xytan0056
xytan0056 self-requested a review August 27, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants