Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions config/service_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@

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
)

// ServiceConfig holds operational configuration for the Tango service.
type ServiceConfig struct {
WorkerPoolSize int `yaml:"worker_pool_size"` // number of worker workspaces per repo
Expand Down
2 changes: 2 additions & 0 deletions controller/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ go_library(
deps = [
"//config",
"//core/common",
"//core/errors",
"//core/storage",
"//orchestrator",
"//tangopb",
Expand All @@ -37,6 +38,7 @@ go_test(
],
embed = [":controller"],
deps = [
"//config",
"//core/common",
"//core/storage",
"//core/storage/storagemock",
Expand Down
7 changes: 3 additions & 4 deletions controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 4 additions & 6 deletions controller/getchangedtargets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -560,9 +561,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.
Expand Down Expand Up @@ -611,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%cancelCheckInterval == 0 && ctx.Err() != nil {
if i%tgerrors.CancelCheckInterval == 0 && ctx.Err() != nil {
return nil, ctx.Err()
}
i++
Expand Down Expand Up @@ -963,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%cancelCheckInterval == 0 && ctx.Err() != nil {
if revDepIter%tgerrors.CancelCheckInterval == 0 && ctx.Err() != nil {
return ctx.Err()
}
revDepIter++
Expand Down Expand Up @@ -991,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%cancelCheckInterval == 0 && ctx.Err() != nil {
if bfsIter%tgerrors.CancelCheckInterval == 0 && ctx.Err() != nil {
return ctx.Err()
}
bfsIter++
Expand Down
8 changes: 4 additions & 4 deletions controller/testhelper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ import (
"context"

"github.com/uber-go/tally"
"github.com/uber/tango/core/common"
"github.com/uber/tango/config"
"go.uber.org/zap"
)

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(),
}
Expand Down
1 change: 1 addition & 0 deletions core/bazel/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ go_library(
importpath = "github.com/uber/tango/core/bazel",
visibility = ["//visibility:public"],
deps = [
"//core/errors",
"//core/execcmd",
"@com_github_bazelbuild_buildtools//build_proto",
"@org_golang_google_protobuf//encoding/protodelim",
Expand Down
9 changes: 3 additions & 6 deletions core/bazel/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (

buildpb "github.com/bazelbuild/buildtools/build_proto"
"google.golang.org/protobuf/encoding/protodelim"

tgerrors "github.com/uber/tango/core/errors"
)

func streamOutput(ctx context.Context, src io.Reader, dst io.Writer) error {
Expand Down Expand Up @@ -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{
Expand All @@ -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%tgerrors.CancelCheckInterval == 0 {
if err := ctx.Err(); err != nil {
return result, err
}
Expand Down
3 changes: 3 additions & 0 deletions core/common/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ go_library(
importpath = "github.com/uber/tango/core/common",
visibility = ["//visibility:public"],
deps = [
"//config",
"//core/errors",
"//core/targethasher",
"//tangopb",
"@com_github_bazelbuild_buildtools//build_proto",
Expand All @@ -24,6 +26,7 @@ go_test(
],
embed = [":common"],
deps = [
"//config",
"//core/targethasher",
"//tangopb",
"@com_github_stretchr_testify//assert",
Expand Down
37 changes: 8 additions & 29 deletions core/common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,12 @@ 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"
)

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 {
Expand Down Expand Up @@ -132,13 +116,8 @@ 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) {
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.
Expand All @@ -157,7 +136,7 @@ func ResultToGetTargetGraphResponse(ctx context.Context, result targethasher.Res

n := 0
for _, t := range result.Targets {
if n%cancelCheckInterval == 0 {
if n%tgerrors.CancelCheckInterval == 0 {
if err := ctx.Err(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -224,14 +203,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, targetChunkSize)
for _, meta := range ChunkMetadata(
targetIDToName,
ruleTypeIDToName,
tagIDToName,
attrNameIDToName,
attrStrValIDToVal,
DefaultMetadataMapChunkSize,
metadataMapChunkSize,
) {
responses = append(responses, &tangopb.GetTargetGraphResponse{
Item: &tangopb.GetTargetGraphResponse_Metadata{Metadata: meta},
Expand All @@ -243,7 +222,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
Expand Down Expand Up @@ -294,7 +273,7 @@ func ChunkMetadata(
chunkSize int,
) []*tangopb.Metadata {
if chunkSize <= 0 {
chunkSize = DefaultMetadataMapChunkSize
chunkSize = config.DefaultMetadataMapChunkSize
}

targetChunks := splitMap(targetIDToName, chunkSize)
Expand Down
3 changes: 2 additions & 1 deletion core/common/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions core/errors/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
load("@rules_go//go:def.bzl", "go_library")

go_library(
name = "errors",
srcs = ["constants.go"],
importpath = "github.com/uber/tango/core/errors",
visibility = ["//visibility:public"],
)
8 changes: 8 additions & 0 deletions core/errors/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package errors

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this really an error package? Should it be some constant package instead

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.

I don't think constant package is a common practice. I think usually the constants are closely related to what the package/functions are doing. In this case, we're polling to see if the context was cancelled (error) during long running loops. But yea I'll update the file to constants.go.


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 = 1024
)
1 change: 1 addition & 0 deletions example/cmd/query-bench/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion example/cmd/query-bench/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down
3 changes: 2 additions & 1 deletion orchestrator/native_orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ 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)

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)
}
Expand Down
Loading