From 6a1a16ad5853327533b61fd70dcfd9689dbd8d07 Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Mon, 6 Jul 2026 22:28:33 -0700 Subject: [PATCH 01/10] Move service and context config to the service_config.go --- config/service_config.go | 23 +++++++++++++++++++++++ controller/getchangedtargets.go | 9 +++------ core/bazel/BUILD.bazel | 1 + core/bazel/stream.go | 9 +++------ core/common/utils.go | 24 +++--------------------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/config/service_config.go b/config/service_config.go index 2245f35..f6fb293 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -14,6 +14,29 @@ package config +const ( + // DefaultTargetChunkSize is the default number of OptimizedTarget entries per stream message. + // Sized conservatively: at ~40KB/target worst-case (target with ~10K direct deps × 4 bytes), + // 250 targets ≈ 10MB — well under the 64MB default gRPC per-message limit. + DefaultTargetChunkSize = 250 + + // DefaultChangedTargetChunkSize is the default number of ChangedTarget entries per stream message. + // A ChangedTarget carries both old_target and new_target (2× an OptimizedTarget), so we use + // half the regular chunk size to stay within the same byte budget. + DefaultChangedTargetChunkSize = 125 + + // DefaultMetadataMapChunkSize is the max entries per metadata message chunk. + // target_id_mapping and attribute_string_value_mapping scale with repo size and can exceed + // the 64MB gRPC message limit for large monorepos, so they are split across multiple messages. + // At ~85 bytes/entry (60-char avg target name + proto overhead), 50 000 entries ≈ 4.25MB per chunk. + DefaultMetadataMapChunkSize = 50_000 + + // CancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. + // Picked to keep overhead negligible while still surfacing cancellation in <100ms + // for typical target rates. + CancelCheckInterval = 4096 +) + // ServiceConfig holds operational configuration for the Tango service. type ServiceConfig struct { WorkerPoolSize int `yaml:"worker_pool_size"` // number of worker workspaces per repo diff --git a/controller/getchangedtargets.go b/controller/getchangedtargets.go index feac4c3..380507b 100644 --- a/controller/getchangedtargets.go +++ b/controller/getchangedtargets.go @@ -560,9 +560,6 @@ func (c *controller) compareTargetGraphs(ctx context.Context, logger *zap.Logger return results, nil } -// cancelCheckInterval is how often long-running loops check ctx.Err(). -const cancelCheckInterval = 4096 - // getTargetsAndMetadata builds ID->target maps and merges metadata from a target graph stream. // Metadata may arrive in multiple chunks (e.g. when target_id_mapping exceeds the gRPC message // size limit); all chunks are merged into a single Metadata so callers can use it uniformly. @@ -611,7 +608,7 @@ func buildNameIndex(ctx context.Context, targetsByID map[int32]*pb.OptimizedTarg byName := make(map[string]*pb.OptimizedTarget, len(targetsByID)) i := 0 for id, t := range targetsByID { - if i%cancelCheckInterval == 0 && ctx.Err() != nil { + if i%common.CancelCheckInterval == 0 && ctx.Err() != nil { return nil, ctx.Err() } i++ @@ -963,7 +960,7 @@ func computeDistances(ctx context.Context, changedByName map[string]*pb.ChangedT reverseDeps := make(map[string][]string, len(targetsByName)) revDepIter := 0 for name, t := range targetsByName { - if revDepIter%cancelCheckInterval == 0 && ctx.Err() != nil { + if revDepIter%common.CancelCheckInterval == 0 && ctx.Err() != nil { return ctx.Err() } revDepIter++ @@ -991,7 +988,7 @@ func computeDistances(ctx context.Context, changedByName map[string]*pb.ChangedT // BFS from seeds through reverseDeps. Shortest distance wins. bfsIter := 0 for len(queue) > 0 { - if bfsIter%cancelCheckInterval == 0 && ctx.Err() != nil { + if bfsIter%common.CancelCheckInterval == 0 && ctx.Err() != nil { return ctx.Err() } bfsIter++ diff --git a/core/bazel/BUILD.bazel b/core/bazel/BUILD.bazel index 3fa695b..ae2606f 100644 --- a/core/bazel/BUILD.bazel +++ b/core/bazel/BUILD.bazel @@ -11,6 +11,7 @@ go_library( importpath = "github.com/uber/tango/core/bazel", visibility = ["//visibility:public"], deps = [ + "//config", "//core/execcmd", "@com_github_bazelbuild_buildtools//build_proto", "@org_golang_google_protobuf//encoding/protodelim", diff --git a/core/bazel/stream.go b/core/bazel/stream.go index 2e78fd7..e57525d 100644 --- a/core/bazel/stream.go +++ b/core/bazel/stream.go @@ -21,6 +21,8 @@ import ( buildpb "github.com/bazelbuild/buildtools/build_proto" "google.golang.org/protobuf/encoding/protodelim" + + "github.com/uber/tango/config" ) func streamOutput(ctx context.Context, src io.Reader, dst io.Writer) error { @@ -58,11 +60,6 @@ func streamAndParseTargets(ctx context.Context, src io.Reader, dst io.Writer) (* } } -// cancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. -// Picked to keep overhead negligible while still surfacing cancellation in <100ms -// for typical target rates. -const cancelCheckInterval = 1024 - // getQueryResult reads a QueryResult containing targets from the stream and returns it. func getQueryResult(ctx context.Context, src io.Reader, dst io.Writer) (*buildpb.QueryResult, error) { result := &buildpb.QueryResult{ @@ -75,7 +72,7 @@ func getQueryResult(ctx context.Context, src io.Reader, dst io.Writer) (*buildpb } var parseErr error for i := 0; ; i++ { - if i%cancelCheckInterval == 0 { + if i%config.CancelCheckInterval == 0 { if err := ctx.Err(); err != nil { return result, err } diff --git a/core/common/utils.go b/core/common/utils.go index 90df802..dc6f584 100644 --- a/core/common/utils.go +++ b/core/common/utils.go @@ -28,24 +28,6 @@ import ( "github.com/uber/tango/tangopb" ) -const ( - // DefaultTargetChunkSize is the default number of OptimizedTarget entries per stream message. - // Sized conservatively: at ~40KB/target worst-case (target with ~10K direct deps × 4 bytes), - // 250 targets ≈ 10MB — well under the 64MB default gRPC per-message limit. - DefaultTargetChunkSize = 250 - - // DefaultChangedTargetChunkSize is the default number of ChangedTarget entries per stream message. - // A ChangedTarget carries both old_target and new_target (2× an OptimizedTarget), so we use - // half the regular chunk size to stay within the same byte budget. - DefaultChangedTargetChunkSize = 125 - - // DefaultMetadataMapChunkSize is the max entries per metadata message chunk. - // target_id_mapping and attribute_string_value_mapping scale with repo size and can exceed - // the 64MB gRPC message limit for large monorepos, so they are split across multiple messages. - // At ~85 bytes/entry (60-char avg target name + proto overhead), 50 000 entries ≈ 4.25MB per chunk. - DefaultMetadataMapChunkSize = 50_000 -) - // ToShortRemote returns the short remote name given a git ssh remote string. // For example, "git@github:uber/tango" will return "uber/tango". func ToShortRemote(remote string) string { @@ -132,10 +114,10 @@ func HashRequestOptions(opts *tangopb.RequestOptions) string { return fmt.Sprintf("%x", h.Sum(nil)) } -// cancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. +// CancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. // Picked to keep overhead negligible while still surfacing cancellation in <100ms // for typical target rates. -const cancelCheckInterval = 4096 +const CancelCheckInterval = 4096 // ResultToGetTargetGraphResponse converts a Result to a GetTargetGraphResponse func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Result) ([]*tangopb.GetTargetGraphResponse, error) { @@ -157,7 +139,7 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res n := 0 for _, t := range result.Targets { - if n%cancelCheckInterval == 0 { + if n%CancelCheckInterval == 0 { if err := ctx.Err(); err != nil { return nil, err } From ce5ad656a8e03cc5b37354ea7b7c5c48146bc2ae Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Mon, 6 Jul 2026 22:34:27 -0700 Subject: [PATCH 02/10] Update --- core/common/utils.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/common/utils.go b/core/common/utils.go index dc6f584..ca214c2 100644 --- a/core/common/utils.go +++ b/core/common/utils.go @@ -114,11 +114,6 @@ func HashRequestOptions(opts *tangopb.RequestOptions) string { return fmt.Sprintf("%x", h.Sum(nil)) } -// CancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. -// Picked to keep overhead negligible while still surfacing cancellation in <100ms -// for typical target rates. -const CancelCheckInterval = 4096 - // ResultToGetTargetGraphResponse converts a Result to a GetTargetGraphResponse func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Result) ([]*tangopb.GetTargetGraphResponse, error) { // Map target names to ids. This list is topologically sorted, so the ids are stable. @@ -139,7 +134,7 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res n := 0 for _, t := range result.Targets { - if n%CancelCheckInterval == 0 { + if n%config.CancelCheckInterval == 0 { if err := ctx.Err(); err != nil { return nil, err } From b92749c096711c6b45a3366d2b1e6853f6a641c9 Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Mon, 6 Jul 2026 22:46:33 -0700 Subject: [PATCH 03/10] Update --- controller/BUILD.bazel | 1 + controller/controller.go | 7 +++---- controller/testhelper_test.go | 8 ++++---- core/common/BUILD.bazel | 1 + core/common/utils.go | 14 ++++++++++---- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/controller/BUILD.bazel b/controller/BUILD.bazel index 581ba3b..107d58e 100644 --- a/controller/BUILD.bazel +++ b/controller/BUILD.bazel @@ -37,6 +37,7 @@ go_test( ], embed = [":controller"], deps = [ + "//config", "//core/common", "//core/storage", "//core/storage/storagemock", diff --git a/controller/controller.go b/controller/controller.go index 34ca33c..1c786c4 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -20,7 +20,6 @@ import ( "github.com/uber-go/tally" "github.com/uber/tango/config" - "github.com/uber/tango/core/common" "github.com/uber/tango/core/storage" "github.com/uber/tango/orchestrator" pb "github.com/uber/tango/tangopb" @@ -66,15 +65,15 @@ func NewController(appCtx context.Context, p Params) pb.TangoYARPCServer { } targetChunkSize := p.ChunkConfig.TargetChunkSize if targetChunkSize <= 0 { - targetChunkSize = common.DefaultTargetChunkSize + targetChunkSize = config.DefaultTargetChunkSize } changedTargetChunkSize := p.ChunkConfig.ChangedTargetChunkSize if changedTargetChunkSize <= 0 { - changedTargetChunkSize = common.DefaultChangedTargetChunkSize + changedTargetChunkSize = config.DefaultChangedTargetChunkSize } metadataMapChunkSize := p.ChunkConfig.MetadataMapChunkSize if metadataMapChunkSize <= 0 { - metadataMapChunkSize = common.DefaultMetadataMapChunkSize + metadataMapChunkSize = config.DefaultMetadataMapChunkSize } return &controller{ logger: p.Logger, diff --git a/controller/testhelper_test.go b/controller/testhelper_test.go index 56ff439..2a11419 100644 --- a/controller/testhelper_test.go +++ b/controller/testhelper_test.go @@ -18,7 +18,7 @@ import ( "context" "github.com/uber-go/tally" - "github.com/uber/tango/core/common" + "github.com/uber/tango/config" "go.uber.org/zap" ) @@ -26,9 +26,9 @@ func newTestController(logger *zap.Logger) *controller { return &controller{ logger: logger, scope: tally.NoopScope, - targetChunkSize: common.DefaultTargetChunkSize, - changedTargetChunkSize: common.DefaultChangedTargetChunkSize, - metadataMapChunkSize: common.DefaultMetadataMapChunkSize, + targetChunkSize: config.DefaultTargetChunkSize, + changedTargetChunkSize: config.DefaultChangedTargetChunkSize, + metadataMapChunkSize: config.DefaultMetadataMapChunkSize, totalDurationBuckets: _totalDurationBuckets, appCtx: context.Background(), } diff --git a/core/common/BUILD.bazel b/core/common/BUILD.bazel index 0daeb3c..13e5818 100644 --- a/core/common/BUILD.bazel +++ b/core/common/BUILD.bazel @@ -10,6 +10,7 @@ go_library( importpath = "github.com/uber/tango/core/common", visibility = ["//visibility:public"], deps = [ + "//config", "//core/targethasher", "//tangopb", "@com_github_bazelbuild_buildtools//build_proto", diff --git a/core/common/utils.go b/core/common/utils.go index ca214c2..baedff6 100644 --- a/core/common/utils.go +++ b/core/common/utils.go @@ -24,6 +24,7 @@ import ( "strings" buildpb "github.com/bazelbuild/buildtools/build_proto" + "github.com/uber/tango/config" "github.com/uber/tango/core/targethasher" "github.com/uber/tango/tangopb" ) @@ -114,6 +115,11 @@ func HashRequestOptions(opts *tangopb.RequestOptions) string { return fmt.Sprintf("%x", h.Sum(nil)) } +// CancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. +// Picked to keep overhead negligible while still surfacing cancellation in <100ms +// for typical target rates. +const CancelCheckInterval = config.CancelCheckInterval + // ResultToGetTargetGraphResponse converts a Result to a GetTargetGraphResponse func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Result) ([]*tangopb.GetTargetGraphResponse, error) { // Map target names to ids. This list is topologically sorted, so the ids are stable. @@ -201,14 +207,14 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res attrStrValIDToVal := attrStrValMapper.Invert() // chunk targets into multiple messages for streaming - responses := chunkTargets(optimizedTargets, DefaultTargetChunkSize) + responses := chunkTargets(optimizedTargets, config.DefaultTargetChunkSize) for _, meta := range ChunkMetadata( targetIDToName, ruleTypeIDToName, tagIDToName, attrNameIDToName, attrStrValIDToVal, - DefaultMetadataMapChunkSize, + config.DefaultMetadataMapChunkSize, ) { responses = append(responses, &tangopb.GetTargetGraphResponse{ Item: &tangopb.GetTargetGraphResponse_Metadata{Metadata: meta}, @@ -220,7 +226,7 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res func chunkTargets(targets []*tangopb.OptimizedTarget, chunkSize int) []*tangopb.GetTargetGraphResponse { if chunkSize <= 0 { - chunkSize = DefaultTargetChunkSize + chunkSize = config.DefaultTargetChunkSize } // at least one chunk @@ -271,7 +277,7 @@ func ChunkMetadata( chunkSize int, ) []*tangopb.Metadata { if chunkSize <= 0 { - chunkSize = DefaultMetadataMapChunkSize + chunkSize = config.DefaultMetadataMapChunkSize } targetChunks := splitMap(targetIDToName, chunkSize) From 480a0bf4faa4a0e061c744c1b52ec5750c3e2c83 Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Tue, 7 Jul 2026 09:57:08 -0700 Subject: [PATCH 04/10] Update --- core/common/utils.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/common/utils.go b/core/common/utils.go index baedff6..979e9a0 100644 --- a/core/common/utils.go +++ b/core/common/utils.go @@ -115,11 +115,6 @@ func HashRequestOptions(opts *tangopb.RequestOptions) string { return fmt.Sprintf("%x", h.Sum(nil)) } -// CancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. -// Picked to keep overhead negligible while still surfacing cancellation in <100ms -// for typical target rates. -const CancelCheckInterval = config.CancelCheckInterval - // ResultToGetTargetGraphResponse converts a Result to a GetTargetGraphResponse func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Result) ([]*tangopb.GetTargetGraphResponse, error) { // Map target names to ids. This list is topologically sorted, so the ids are stable. From ae143249dd17d9de92ec05008a00950ac6447739 Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Tue, 7 Jul 2026 10:13:45 -0700 Subject: [PATCH 05/10] errors directory --- config/service_config.go | 5 ----- controller/BUILD.bazel | 1 + controller/controller.go | 6 +++--- controller/getchangedtargets.go | 7 ++++--- controller/testhelper_test.go | 6 +++--- core/bazel/BUILD.bazel | 2 +- core/bazel/stream.go | 4 ++-- core/common/BUILD.bazel | 1 + core/common/utils.go | 11 ++++++----- core/errors/BUILD.bazel | 8 ++++++++ core/errors/errors.go | 24 ++++++++++++++++++++++++ 11 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 core/errors/BUILD.bazel create mode 100644 core/errors/errors.go diff --git a/config/service_config.go b/config/service_config.go index f6fb293..c551346 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -30,11 +30,6 @@ const ( // the 64MB gRPC message limit for large monorepos, so they are split across multiple messages. // At ~85 bytes/entry (60-char avg target name + proto overhead), 50 000 entries ≈ 4.25MB per chunk. DefaultMetadataMapChunkSize = 50_000 - - // CancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. - // Picked to keep overhead negligible while still surfacing cancellation in <100ms - // for typical target rates. - CancelCheckInterval = 4096 ) // ServiceConfig holds operational configuration for the Tango service. diff --git a/controller/BUILD.bazel b/controller/BUILD.bazel index 107d58e..23609fd 100644 --- a/controller/BUILD.bazel +++ b/controller/BUILD.bazel @@ -16,6 +16,7 @@ go_library( deps = [ "//config", "//core/common", + "//core/errors", "//core/storage", "//orchestrator", "//tangopb", diff --git a/controller/controller.go b/controller/controller.go index 1c786c4..469f857 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -65,15 +65,15 @@ func NewController(appCtx context.Context, p Params) pb.TangoYARPCServer { } targetChunkSize := p.ChunkConfig.TargetChunkSize if targetChunkSize <= 0 { - targetChunkSize = config.DefaultTargetChunkSize + targetChunkSize = tgerrors.DefaultTargetChunkSize } changedTargetChunkSize := p.ChunkConfig.ChangedTargetChunkSize if changedTargetChunkSize <= 0 { - changedTargetChunkSize = config.DefaultChangedTargetChunkSize + changedTargetChunkSize = tgerrors.DefaultChangedTargetChunkSize } metadataMapChunkSize := p.ChunkConfig.MetadataMapChunkSize if metadataMapChunkSize <= 0 { - metadataMapChunkSize = config.DefaultMetadataMapChunkSize + metadataMapChunkSize = tgerrors.DefaultMetadataMapChunkSize } return &controller{ logger: p.Logger, diff --git a/controller/getchangedtargets.go b/controller/getchangedtargets.go index 380507b..42ae142 100644 --- a/controller/getchangedtargets.go +++ b/controller/getchangedtargets.go @@ -22,6 +22,7 @@ import ( "time" "github.com/uber/tango/core/common" + tgerrors "github.com/uber/tango/core/errors" "github.com/uber/tango/core/storage" pb "github.com/uber/tango/tangopb" "go.uber.org/zap" @@ -608,7 +609,7 @@ func buildNameIndex(ctx context.Context, targetsByID map[int32]*pb.OptimizedTarg byName := make(map[string]*pb.OptimizedTarget, len(targetsByID)) i := 0 for id, t := range targetsByID { - if i%common.CancelCheckInterval == 0 && ctx.Err() != nil { + if i%tgerrors.CancelCheckInterval == 0 && ctx.Err() != nil { return nil, ctx.Err() } i++ @@ -960,7 +961,7 @@ func computeDistances(ctx context.Context, changedByName map[string]*pb.ChangedT reverseDeps := make(map[string][]string, len(targetsByName)) revDepIter := 0 for name, t := range targetsByName { - if revDepIter%common.CancelCheckInterval == 0 && ctx.Err() != nil { + if revDepIter%tgerrors.CancelCheckInterval == 0 && ctx.Err() != nil { return ctx.Err() } revDepIter++ @@ -988,7 +989,7 @@ func computeDistances(ctx context.Context, changedByName map[string]*pb.ChangedT // BFS from seeds through reverseDeps. Shortest distance wins. bfsIter := 0 for len(queue) > 0 { - if bfsIter%common.CancelCheckInterval == 0 && ctx.Err() != nil { + if bfsIter%tgerrors.CancelCheckInterval == 0 && ctx.Err() != nil { return ctx.Err() } bfsIter++ diff --git a/controller/testhelper_test.go b/controller/testhelper_test.go index 2a11419..660032b 100644 --- a/controller/testhelper_test.go +++ b/controller/testhelper_test.go @@ -26,9 +26,9 @@ func newTestController(logger *zap.Logger) *controller { return &controller{ logger: logger, scope: tally.NoopScope, - targetChunkSize: config.DefaultTargetChunkSize, - changedTargetChunkSize: config.DefaultChangedTargetChunkSize, - metadataMapChunkSize: config.DefaultMetadataMapChunkSize, + targetChunkSize: tgerrors.DefaultTargetChunkSize, + changedTargetChunkSize: tgerrors.DefaultChangedTargetChunkSize, + metadataMapChunkSize: tgerrors.DefaultMetadataMapChunkSize, totalDurationBuckets: _totalDurationBuckets, appCtx: context.Background(), } diff --git a/core/bazel/BUILD.bazel b/core/bazel/BUILD.bazel index ae2606f..8772704 100644 --- a/core/bazel/BUILD.bazel +++ b/core/bazel/BUILD.bazel @@ -11,7 +11,7 @@ go_library( importpath = "github.com/uber/tango/core/bazel", visibility = ["//visibility:public"], deps = [ - "//config", + "//core/errors", "//core/execcmd", "@com_github_bazelbuild_buildtools//build_proto", "@org_golang_google_protobuf//encoding/protodelim", diff --git a/core/bazel/stream.go b/core/bazel/stream.go index e57525d..4da83cd 100644 --- a/core/bazel/stream.go +++ b/core/bazel/stream.go @@ -22,7 +22,7 @@ import ( buildpb "github.com/bazelbuild/buildtools/build_proto" "google.golang.org/protobuf/encoding/protodelim" - "github.com/uber/tango/config" + tgerrors "github.com/uber/tango/core/errors" ) func streamOutput(ctx context.Context, src io.Reader, dst io.Writer) error { @@ -72,7 +72,7 @@ func getQueryResult(ctx context.Context, src io.Reader, dst io.Writer) (*buildpb } var parseErr error for i := 0; ; i++ { - if i%config.CancelCheckInterval == 0 { + if i%tgerrors.CancelCheckInterval == 0 { if err := ctx.Err(); err != nil { return result, err } diff --git a/core/common/BUILD.bazel b/core/common/BUILD.bazel index 13e5818..055d4c0 100644 --- a/core/common/BUILD.bazel +++ b/core/common/BUILD.bazel @@ -11,6 +11,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//config", + "//core/errors", "//core/targethasher", "//tangopb", "@com_github_bazelbuild_buildtools//build_proto", diff --git a/core/common/utils.go b/core/common/utils.go index 979e9a0..ce21eba 100644 --- a/core/common/utils.go +++ b/core/common/utils.go @@ -25,6 +25,7 @@ import ( buildpb "github.com/bazelbuild/buildtools/build_proto" "github.com/uber/tango/config" + tgerrors "github.com/uber/tango/core/errors" "github.com/uber/tango/core/targethasher" "github.com/uber/tango/tangopb" ) @@ -135,7 +136,7 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res n := 0 for _, t := range result.Targets { - if n%config.CancelCheckInterval == 0 { + if n%tgerrors.CancelCheckInterval == 0 { if err := ctx.Err(); err != nil { return nil, err } @@ -202,14 +203,14 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res attrStrValIDToVal := attrStrValMapper.Invert() // chunk targets into multiple messages for streaming - responses := chunkTargets(optimizedTargets, config.DefaultTargetChunkSize) + responses := chunkTargets(optimizedTargets, tgerrors.DefaultTargetChunkSize) for _, meta := range ChunkMetadata( targetIDToName, ruleTypeIDToName, tagIDToName, attrNameIDToName, attrStrValIDToVal, - config.DefaultMetadataMapChunkSize, + tgerrors.DefaultMetadataMapChunkSize, ) { responses = append(responses, &tangopb.GetTargetGraphResponse{ Item: &tangopb.GetTargetGraphResponse_Metadata{Metadata: meta}, @@ -221,7 +222,7 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res func chunkTargets(targets []*tangopb.OptimizedTarget, chunkSize int) []*tangopb.GetTargetGraphResponse { if chunkSize <= 0 { - chunkSize = config.DefaultTargetChunkSize + chunkSize = tgerrors.DefaultTargetChunkSize } // at least one chunk @@ -272,7 +273,7 @@ func ChunkMetadata( chunkSize int, ) []*tangopb.Metadata { if chunkSize <= 0 { - chunkSize = config.DefaultMetadataMapChunkSize + chunkSize = tgerrors.DefaultMetadataMapChunkSize } targetChunks := splitMap(targetIDToName, chunkSize) diff --git a/core/errors/BUILD.bazel b/core/errors/BUILD.bazel new file mode 100644 index 0000000..a6526bd --- /dev/null +++ b/core/errors/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "errors", + srcs = ["errors.go"], + importpath = "github.com/uber/tango/core/errors", + visibility = ["//visibility:public"], +) diff --git a/core/errors/errors.go b/core/errors/errors.go new file mode 100644 index 0000000..caa8fdc --- /dev/null +++ b/core/errors/errors.go @@ -0,0 +1,24 @@ +package errors + +const ( + // CancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. + // Picked to keep overhead negligible while still surfacing cancellation in <100ms + // for typical target rates. + CancelCheckInterval = 4096 + + // DefaultTargetChunkSize is the default number of OptimizedTarget entries per stream message. + // Sized conservatively: at ~40KB/target worst-case (target with ~10K direct deps × 4 bytes), + // 250 targets ≈ 10MB — well under the 64MB default gRPC per-message limit. + DefaultTargetChunkSize = 250 + + // DefaultChangedTargetChunkSize is the default number of ChangedTarget entries per stream message. + // A ChangedTarget carries both old_target and new_target (2× an OptimizedTarget), so we use + // half the regular chunk size to stay within the same byte budget. + DefaultChangedTargetChunkSize = 125 + + // DefaultMetadataMapChunkSize is the max entries per metadata message chunk. + // target_id_mapping and attribute_string_value_mapping scale with repo size and can exceed + // the 64MB gRPC message limit for large monorepos, so they are split across multiple messages. + // At ~85 bytes/entry (60-char avg target name + proto overhead), 50 000 entries ≈ 4.25MB per chunk. + DefaultMetadataMapChunkSize = 50_000 +) From 769d0e59fd53701c61ba075740ab573987f1dabd Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Tue, 7 Jul 2026 10:16:23 -0700 Subject: [PATCH 06/10] Update --- controller/BUILD.bazel | 2 +- controller/controller.go | 1 + controller/testhelper_test.go | 2 +- core/common/BUILD.bazel | 1 - core/common/utils.go | 1 - 5 files changed, 3 insertions(+), 4 deletions(-) diff --git a/controller/BUILD.bazel b/controller/BUILD.bazel index 23609fd..41018e5 100644 --- a/controller/BUILD.bazel +++ b/controller/BUILD.bazel @@ -38,8 +38,8 @@ go_test( ], embed = [":controller"], deps = [ - "//config", "//core/common", + "//core/errors", "//core/storage", "//core/storage/storagemock", "//orchestrator/orchestratormock", diff --git a/controller/controller.go b/controller/controller.go index 469f857..83fbf11 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -20,6 +20,7 @@ import ( "github.com/uber-go/tally" "github.com/uber/tango/config" + tgerrors "github.com/uber/tango/core/errors" "github.com/uber/tango/core/storage" "github.com/uber/tango/orchestrator" pb "github.com/uber/tango/tangopb" diff --git a/controller/testhelper_test.go b/controller/testhelper_test.go index 660032b..7b04c70 100644 --- a/controller/testhelper_test.go +++ b/controller/testhelper_test.go @@ -18,7 +18,7 @@ import ( "context" "github.com/uber-go/tally" - "github.com/uber/tango/config" + tgerrors "github.com/uber/tango/core/errors" "go.uber.org/zap" ) diff --git a/core/common/BUILD.bazel b/core/common/BUILD.bazel index 055d4c0..4a20a1a 100644 --- a/core/common/BUILD.bazel +++ b/core/common/BUILD.bazel @@ -10,7 +10,6 @@ go_library( importpath = "github.com/uber/tango/core/common", visibility = ["//visibility:public"], deps = [ - "//config", "//core/errors", "//core/targethasher", "//tangopb", diff --git a/core/common/utils.go b/core/common/utils.go index ce21eba..b72ddd7 100644 --- a/core/common/utils.go +++ b/core/common/utils.go @@ -24,7 +24,6 @@ import ( "strings" buildpb "github.com/bazelbuild/buildtools/build_proto" - "github.com/uber/tango/config" tgerrors "github.com/uber/tango/core/errors" "github.com/uber/tango/core/targethasher" "github.com/uber/tango/tangopb" From 047bfa612f8f93aa8ffa3eb8b222d56352fc0605 Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Tue, 7 Jul 2026 10:23:04 -0700 Subject: [PATCH 07/10] Update --- controller/BUILD.bazel | 2 +- controller/controller.go | 7 +++---- controller/testhelper_test.go | 8 ++++---- core/common/BUILD.bazel | 1 + core/common/utils.go | 9 +++++---- core/errors/errors.go | 16 ---------------- 6 files changed, 14 insertions(+), 29 deletions(-) diff --git a/controller/BUILD.bazel b/controller/BUILD.bazel index 41018e5..23609fd 100644 --- a/controller/BUILD.bazel +++ b/controller/BUILD.bazel @@ -38,8 +38,8 @@ go_test( ], embed = [":controller"], deps = [ + "//config", "//core/common", - "//core/errors", "//core/storage", "//core/storage/storagemock", "//orchestrator/orchestratormock", diff --git a/controller/controller.go b/controller/controller.go index 83fbf11..1c786c4 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -20,7 +20,6 @@ import ( "github.com/uber-go/tally" "github.com/uber/tango/config" - tgerrors "github.com/uber/tango/core/errors" "github.com/uber/tango/core/storage" "github.com/uber/tango/orchestrator" pb "github.com/uber/tango/tangopb" @@ -66,15 +65,15 @@ func NewController(appCtx context.Context, p Params) pb.TangoYARPCServer { } targetChunkSize := p.ChunkConfig.TargetChunkSize if targetChunkSize <= 0 { - targetChunkSize = tgerrors.DefaultTargetChunkSize + targetChunkSize = config.DefaultTargetChunkSize } changedTargetChunkSize := p.ChunkConfig.ChangedTargetChunkSize if changedTargetChunkSize <= 0 { - changedTargetChunkSize = tgerrors.DefaultChangedTargetChunkSize + changedTargetChunkSize = config.DefaultChangedTargetChunkSize } metadataMapChunkSize := p.ChunkConfig.MetadataMapChunkSize if metadataMapChunkSize <= 0 { - metadataMapChunkSize = tgerrors.DefaultMetadataMapChunkSize + metadataMapChunkSize = config.DefaultMetadataMapChunkSize } return &controller{ logger: p.Logger, diff --git a/controller/testhelper_test.go b/controller/testhelper_test.go index 7b04c70..2a11419 100644 --- a/controller/testhelper_test.go +++ b/controller/testhelper_test.go @@ -18,7 +18,7 @@ import ( "context" "github.com/uber-go/tally" - tgerrors "github.com/uber/tango/core/errors" + "github.com/uber/tango/config" "go.uber.org/zap" ) @@ -26,9 +26,9 @@ func newTestController(logger *zap.Logger) *controller { return &controller{ logger: logger, scope: tally.NoopScope, - targetChunkSize: tgerrors.DefaultTargetChunkSize, - changedTargetChunkSize: tgerrors.DefaultChangedTargetChunkSize, - metadataMapChunkSize: tgerrors.DefaultMetadataMapChunkSize, + targetChunkSize: config.DefaultTargetChunkSize, + changedTargetChunkSize: config.DefaultChangedTargetChunkSize, + metadataMapChunkSize: config.DefaultMetadataMapChunkSize, totalDurationBuckets: _totalDurationBuckets, appCtx: context.Background(), } diff --git a/core/common/BUILD.bazel b/core/common/BUILD.bazel index 4a20a1a..055d4c0 100644 --- a/core/common/BUILD.bazel +++ b/core/common/BUILD.bazel @@ -10,6 +10,7 @@ go_library( importpath = "github.com/uber/tango/core/common", visibility = ["//visibility:public"], deps = [ + "//config", "//core/errors", "//core/targethasher", "//tangopb", diff --git a/core/common/utils.go b/core/common/utils.go index b72ddd7..b6286c1 100644 --- a/core/common/utils.go +++ b/core/common/utils.go @@ -24,6 +24,7 @@ import ( "strings" buildpb "github.com/bazelbuild/buildtools/build_proto" + "github.com/uber/tango/config" tgerrors "github.com/uber/tango/core/errors" "github.com/uber/tango/core/targethasher" "github.com/uber/tango/tangopb" @@ -202,14 +203,14 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res attrStrValIDToVal := attrStrValMapper.Invert() // chunk targets into multiple messages for streaming - responses := chunkTargets(optimizedTargets, tgerrors.DefaultTargetChunkSize) + responses := chunkTargets(optimizedTargets, config.DefaultTargetChunkSize) for _, meta := range ChunkMetadata( targetIDToName, ruleTypeIDToName, tagIDToName, attrNameIDToName, attrStrValIDToVal, - tgerrors.DefaultMetadataMapChunkSize, + config.DefaultMetadataMapChunkSize, ) { responses = append(responses, &tangopb.GetTargetGraphResponse{ Item: &tangopb.GetTargetGraphResponse_Metadata{Metadata: meta}, @@ -221,7 +222,7 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res func chunkTargets(targets []*tangopb.OptimizedTarget, chunkSize int) []*tangopb.GetTargetGraphResponse { if chunkSize <= 0 { - chunkSize = tgerrors.DefaultTargetChunkSize + chunkSize = config.DefaultTargetChunkSize } // at least one chunk @@ -272,7 +273,7 @@ func ChunkMetadata( chunkSize int, ) []*tangopb.Metadata { if chunkSize <= 0 { - chunkSize = tgerrors.DefaultMetadataMapChunkSize + chunkSize = config.DefaultMetadataMapChunkSize } targetChunks := splitMap(targetIDToName, chunkSize) diff --git a/core/errors/errors.go b/core/errors/errors.go index caa8fdc..11e9e53 100644 --- a/core/errors/errors.go +++ b/core/errors/errors.go @@ -5,20 +5,4 @@ const ( // Picked to keep overhead negligible while still surfacing cancellation in <100ms // for typical target rates. CancelCheckInterval = 4096 - - // DefaultTargetChunkSize is the default number of OptimizedTarget entries per stream message. - // Sized conservatively: at ~40KB/target worst-case (target with ~10K direct deps × 4 bytes), - // 250 targets ≈ 10MB — well under the 64MB default gRPC per-message limit. - DefaultTargetChunkSize = 250 - - // DefaultChangedTargetChunkSize is the default number of ChangedTarget entries per stream message. - // A ChangedTarget carries both old_target and new_target (2× an OptimizedTarget), so we use - // half the regular chunk size to stay within the same byte budget. - DefaultChangedTargetChunkSize = 125 - - // DefaultMetadataMapChunkSize is the max entries per metadata message chunk. - // target_id_mapping and attribute_string_value_mapping scale with repo size and can exceed - // the 64MB gRPC message limit for large monorepos, so they are split across multiple messages. - // At ~85 bytes/entry (60-char avg target name + proto overhead), 50 000 entries ≈ 4.25MB per chunk. - DefaultMetadataMapChunkSize = 50_000 ) From ae6d075fa33b8f0070f709a5b5af831f40407971 Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Wed, 8 Jul 2026 08:40:01 -0700 Subject: [PATCH 08/10] Update --- core/common/utils.go | 6 +++--- core/common/utils_test.go | 3 ++- core/errors/BUILD.bazel | 2 +- core/errors/{errors.go => constants.go} | 2 +- example/cmd/query-bench/BUILD.bazel | 1 + example/cmd/query-bench/main.go | 3 ++- orchestrator/native_orchestrator.go | 10 +++++++++- 7 files changed, 19 insertions(+), 8 deletions(-) rename core/errors/{errors.go => constants.go} (88%) diff --git a/core/common/utils.go b/core/common/utils.go index b6286c1..3edcc04 100644 --- a/core/common/utils.go +++ b/core/common/utils.go @@ -117,7 +117,7 @@ func HashRequestOptions(opts *tangopb.RequestOptions) string { } // ResultToGetTargetGraphResponse converts a Result to a GetTargetGraphResponse -func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Result) ([]*tangopb.GetTargetGraphResponse, error) { +func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Result, targetChunkSize, metadataMapChunkSize int) ([]*tangopb.GetTargetGraphResponse, error) { // Map target names to ids. This list is topologically sorted, so the ids are stable. // IDs start at 1 — 0 is reserved as the proto3 "unset" sentinel so consumers using // encoding/json (which honors `omitempty` on int32 fields) never silently lose a target. @@ -203,14 +203,14 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res attrStrValIDToVal := attrStrValMapper.Invert() // chunk targets into multiple messages for streaming - responses := chunkTargets(optimizedTargets, config.DefaultTargetChunkSize) + responses := chunkTargets(optimizedTargets, targetChunkSize) for _, meta := range ChunkMetadata( targetIDToName, ruleTypeIDToName, tagIDToName, attrNameIDToName, attrStrValIDToVal, - config.DefaultMetadataMapChunkSize, + metadataMapChunkSize, ) { responses = append(responses, &tangopb.GetTargetGraphResponse{ Item: &tangopb.GetTargetGraphResponse_Metadata{Metadata: meta}, diff --git a/core/common/utils_test.go b/core/common/utils_test.go index 4172da0..6ff5aa1 100644 --- a/core/common/utils_test.go +++ b/core/common/utils_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/uber/tango/config" "github.com/uber/tango/core/targethasher" pb "github.com/uber/tango/tangopb" ) @@ -198,7 +199,7 @@ func TestResultToGetTargetGraphResponse_Chunking(t *testing.T) { result.Targets[name] = &targethasher.Target{Name: name, Hash: []byte{0}, RuleType: "go_library"} } - responses, err := ResultToGetTargetGraphResponse(context.Background(), result) + responses, err := ResultToGetTargetGraphResponse(context.Background(), result, config.DefaultTargetChunkSize, config.DefaultMetadataMapChunkSize) require.NoError(t, err) // 2 target chunks + 1 metadata chunk (500 targets well under DefaultMetadataMapChunkSize) diff --git a/core/errors/BUILD.bazel b/core/errors/BUILD.bazel index a6526bd..71c7646 100644 --- a/core/errors/BUILD.bazel +++ b/core/errors/BUILD.bazel @@ -2,7 +2,7 @@ load("@rules_go//go:def.bzl", "go_library") go_library( name = "errors", - srcs = ["errors.go"], + srcs = ["constants.go"], importpath = "github.com/uber/tango/core/errors", visibility = ["//visibility:public"], ) diff --git a/core/errors/errors.go b/core/errors/constants.go similarity index 88% rename from core/errors/errors.go rename to core/errors/constants.go index 11e9e53..4483969 100644 --- a/core/errors/errors.go +++ b/core/errors/constants.go @@ -4,5 +4,5 @@ const ( // CancelCheckInterval is how often we poll ctx.Err() inside per-target hot loops. // Picked to keep overhead negligible while still surfacing cancellation in <100ms // for typical target rates. - CancelCheckInterval = 4096 + CancelCheckInterval = 1024 ) diff --git a/example/cmd/query-bench/BUILD.bazel b/example/cmd/query-bench/BUILD.bazel index ba71ce1..6c798b8 100644 --- a/example/cmd/query-bench/BUILD.bazel +++ b/example/cmd/query-bench/BUILD.bazel @@ -6,6 +6,7 @@ go_library( importpath = "github.com/uber/tango/example/cmd/query-bench", visibility = ["//visibility:private"], deps = [ + "//config", "//core/bazel", "//core/common", "//core/targethasher", diff --git a/example/cmd/query-bench/main.go b/example/cmd/query-bench/main.go index 3071b01..a766386 100644 --- a/example/cmd/query-bench/main.go +++ b/example/cmd/query-bench/main.go @@ -31,6 +31,7 @@ import ( "time" "github.com/gogo/protobuf/jsonpb" + "github.com/uber/tango/config" "github.com/uber/tango/core/bazel" "github.com/uber/tango/core/common" "github.com/uber/tango/core/targethasher" @@ -112,7 +113,7 @@ func run() error { totalDuration += elapsed fmt.Printf("run %d: targethasher: %v (%d targets)\n", i+1, elapsed.Round(time.Millisecond), len(targethasherResult.TargetNames)) start = time.Now() - response, err := common.ResultToGetTargetGraphResponse(ctx, targethasherResult) // also print the time to convert to GetTargetGraphResponse format + response, err := common.ResultToGetTargetGraphResponse(ctx, targethasherResult, config.DefaultTargetChunkSize, config.DefaultMetadataMapChunkSize) if err != nil { return fmt.Errorf("run %d: converting to GetTargetGraphResponse: %w", i+1, err) } diff --git a/orchestrator/native_orchestrator.go b/orchestrator/native_orchestrator.go index 4558cff..7bc3919 100644 --- a/orchestrator/native_orchestrator.go +++ b/orchestrator/native_orchestrator.go @@ -208,7 +208,15 @@ func (b *nativeOrchestrator) GetTargetGraph(ctx context.Context, param GetTarget if err != nil { return nil, fmt.Errorf("compute target graph: %w", err) } - responses, err := common.ResultToGetTargetGraphResponse(ctx, result) + targetChunkSize := b.config.Service.Chunking.TargetChunkSize + if targetChunkSize <= 0 { + targetChunkSize = config.DefaultTargetChunkSize + } + metadataMapChunkSize := b.config.Service.Chunking.MetadataMapChunkSize + if metadataMapChunkSize <= 0 { + metadataMapChunkSize = config.DefaultMetadataMapChunkSize + } + responses, err := common.ResultToGetTargetGraphResponse(ctx, result, targetChunkSize, metadataMapChunkSize) if err != nil { return nil, fmt.Errorf("convert target graph to response: %w", err) } From 842e144deb25b6366b8e0f8d5069df6bd26e603a Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Wed, 8 Jul 2026 10:13:57 -0700 Subject: [PATCH 09/10] Update --- orchestrator/native_orchestrator.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/orchestrator/native_orchestrator.go b/orchestrator/native_orchestrator.go index 7bc3919..4c45987 100644 --- a/orchestrator/native_orchestrator.go +++ b/orchestrator/native_orchestrator.go @@ -208,15 +208,8 @@ func (b *nativeOrchestrator) GetTargetGraph(ctx context.Context, param GetTarget if err != nil { return nil, fmt.Errorf("compute target graph: %w", err) } - targetChunkSize := b.config.Service.Chunking.TargetChunkSize - if targetChunkSize <= 0 { - targetChunkSize = config.DefaultTargetChunkSize - } - metadataMapChunkSize := b.config.Service.Chunking.MetadataMapChunkSize - if metadataMapChunkSize <= 0 { - metadataMapChunkSize = config.DefaultMetadataMapChunkSize - } - responses, err := common.ResultToGetTargetGraphResponse(ctx, result, targetChunkSize, metadataMapChunkSize) + + responses, err := common.ResultToGetTargetGraphResponse(ctx, result, b.config.Service.Chunking.TargetChunkSize, b.config.Service.Chunking.MetadataMapChunkSize) if err != nil { return nil, fmt.Errorf("convert target graph to response: %w", err) } From 1df0504971456b8a54e2f3c9049b0b7ede623456 Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Wed, 8 Jul 2026 10:14:21 -0700 Subject: [PATCH 10/10] Update --- core/common/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/core/common/BUILD.bazel b/core/common/BUILD.bazel index 055d4c0..35fc9a2 100644 --- a/core/common/BUILD.bazel +++ b/core/common/BUILD.bazel @@ -26,6 +26,7 @@ go_test( ], embed = [":common"], deps = [ + "//config", "//core/targethasher", "//tangopb", "@com_github_stretchr_testify//assert",