Skip to content

refactor(controller): use internal/targetdiff for GetChangedTargets#174

Open
xytan0056 wants to merge 7 commits into
mainfrom
refactor/getchangedtargets-use-targetdiff
Open

refactor(controller): use internal/targetdiff for GetChangedTargets#174
xytan0056 wants to merge 7 commits into
mainfrom
refactor/getchangedtargets-use-targetdiff

Conversation

@xytan0056

@xytan0056 xytan0056 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Now that we have internal/compare.go, move the diff/classify/BFS logic in getchangedtargets.go to internal/targetdiff.Compare. The controller decodes each graph stream into a semantic graph, compares, and re-maps. Behavior not changed.

  • make getchangedtargets Controller call internal/targetdiff
  • break getchangedtargets to component functions

Test

make test
Manually tested locally

make run-client-changed-targets REMOTE=https://github.com/uber/tango.git BASE_SHA=74d1cd55155e5f4f43aa92b4e0146a0c528a0d96  NEW_BASE_SHA=74d1cd55155e5f4f43aa92b4e0146a0c528a0d96~
//controller:controller_test: {"distance":3}

bazel query "somepath(//controller:controller_test , //orchestrator:native_orchestrator.go )"
//controller:controller_test
//orchestrator/orchestratormock:orchestratormock
//orchestrator:orchestrator
//orchestrator:native_orchestrator.go

Replace the open-coded diff/classify/BFS logic in getchangedtargets.go
with internal/targetdiff.Compare. The controller now decodes each graph
stream into a semantic targetdiff.Graph, compares, and re-maps the result
into canonical wire IDs. Behavior is unchanged.
@xytan0056 xytan0056 requested review from a team as code owners July 10, 2026 16:41
xytan0056 and others added 2 commits July 10, 2026 10:04
Extract the cache-serve, concurrent graph-fetch, and fire-and-forget
cache-write phases into serveChangedTargetsFromCache, fetchTargetGraphs,
and cacheComparedTargets. GetChangedTargets drops from ~280 to ~85 lines
and now reads as a linear pipeline. Behavior is unchanged.
logger.Info("GetChangedTargets: Cache hit, streaming from storage",
zap.Duration("cache_read_duration", cacheReadDuration),
)
scope.Counter("changed_targets_cache_hit").Inc(1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

keep the old metric recording here until the metrics package is ready

@xytan0056 xytan0056 changed the title [WIP] refactor(controller): use internal/targetdiff for GetChangedTargets refactor(controller): use internal/targetdiff for GetChangedTargets Jul 10, 2026
// GetChangedTargets returns the changed targets between two revisions. If the
// client disconnects, the stream's context is cancelled and the function
// returns with context.Canceled.
func (c *controller) GetChangedTargets(request *pb.GetChangedTargetsRequest, stream pb.TangoServiceGetChangedTargetsYARPCServer) (retErr error) {

@xytan0056 xytan0056 Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

now the call flow is
GetChangedTargets
-> serveChangedTargetsFromCache
-> fetchTargetGraphs for both revs
-> compareTargetGraphs
-> transpose
-> cacheComparedTargets

Comment thread controller/getchangedtargets.go
if err != nil {
logger.Error("GetChangedTargets: Failed to read revision treehash", zap.Error(err))
return common.WithReason(failureReasonTreehashRead, common.ErrorTypeInfra, err)
served, err := c.serveChangedTargetsFromCache(ctx, scope, logger, request, stream, maxDist, start)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just to confirm, with the refactoring to scope and logger, we won't need to provide this as input everywhere (Initially I was wondering why aren't these set as fields on the controller where we can do c.scope(), c.logger()

@xytan0056 xytan0056 Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, these shouldn't have been passed as arguments -- will be further refactored once metrics package is ready to use. We will extract scope from ctx.
For now, because the ctx is not there yet, we need to pass them in like this, we cannot share the scope from controller struct like c., because each of these methods technically are child scope, from parent, which doesn't carry some enriched infomation.

Comment thread controller/getchangedtargets.go Outdated
Comment thread controller/getchangedtargets.go Outdated
Comment on lines +110 to +111
// Cache the computed result concurrently so it doesn't block the stream send.
c.cacheComparedTargets(logger, request, changedTargetsResponses)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

IMO it should be the responsibility of the top level method to decide whether to cache concurrently (In a goroutine or not).

The underlying helper method should just do the caching comparing logic, not make decision on whether it should be in a goroutine or not

@xytan0056 xytan0056 Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

oh this is not comparing, this is to fire and forget function to cache the ctc result. so that the next request for the same treehash compare could return fast from cache
So the "goroutine or not" decision is supposed to be determined in the underlying function (reason being use separte appCtx + best effort + caller not changing response, these re nothing to do with caller)

Comment thread controller/getchangedtargets.go
Comment thread controller/getchangedtargets.go
xytan0056 and others added 2 commits July 10, 2026 15:25
…rify cache flow

Move the compare_duration timer and completion log into compareTargetGraphs
so it is self-contained like fetchTargetGraphs, rather than split across the
caller. Error classification stays at the handler per the error-classification
contract. Also split the cache fast-path return into explicit served/miss/fail
branches for readability.
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.

2 participants