Skip to content

Conversation

yunmaoQu
Copy link

Which problem is this PR solving?

Resolves #5911

Description of the changes

  • add dependency processor using Apache Beam

How was this change tested?

  • e2e tests

Checklist

@yunmaoQu yunmaoQu requested a review from a team as a code owner January 17, 2025 17:37
@yunmaoQu yunmaoQu requested a review from joe-elliott January 17, 2025 17:37
@yunmaoQu yunmaoQu force-pushed the add-dependency-processor branch from af5f794 to 60fb334 Compare January 17, 2025 17:42
Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

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

  • where is it hooked up to anything?
  • what would be the e2e testing for this component?

@yunmaoQu
Copy link
Author

yunmaoQu commented Jan 18, 2025

  • where is it hooked up to anything?
  • what would be the e2e testing for this component?

@yurishkuro I have fixed it

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

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

@mahadzaryab1 interesting direction here

Signed-off-by: yunmaoQu <[email protected]>
@yunmaoQu
Copy link
Author

yunmaoQu commented Jan 20, 2025

@yurishkuro Except this ,I update all based on your review.

config *Config
aggregator *dependencyAggregator // Define the aggregator below.
telset component.TelemetrySettings
dependencyWriter *memory.Store
Copy link
Member

Choose a reason for hiding this comment

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

as I mentioned, you cannot have concrete store dependency here. The processor needs to work with any storage supported by Jaeger, as long as they implement WriteDependencies.

Example:

f, err := jaegerstorage.GetStorageFactory(storageName, host)

func (tp *dependencyProcessor) Shutdown(ctx context.Context) error {
close(tp.closeChan)
if tp.aggregator != nil {
if err := tp.aggregator.Close(); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

if aggregator has a Close() function why does it need to be passed closeChan?

Copy link
Member

Choose a reason for hiding this comment

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

?

// is considered complete and ready for dependency aggregation.
// Default trace completion timeout: 2 seconds of inactivity
InactivityTimeout time.Duration `yaml:"inactivity_timeout"`
}
Copy link
Member

Choose a reason for hiding this comment

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

please add Validate method and use valid: notations in the field tags.

Copy link
Author

Choose a reason for hiding this comment

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

ok

Copy link
Member

Choose a reason for hiding this comment

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

this is not what I meant - these validations can all be done automatically via "github.com/asaskevich/govalidator" & govalidator.ValidateStruct(cfg)

e.g. cmd/jaeger/internal/extension/jaegerquery/config.go

Signed-off-by: yunmaoQu <[email protected]>
@yunmaoQu
Copy link
Author

yunmaoQu commented Jan 30, 2025

@yurishkuro I have fixed it

Signed-off-by: yunmaoQu <[email protected]>
Comment on lines 57 to 58
func (agg *dependencyAggregator) Start(closeChan chan struct{}) {
agg.closeChan = closeChan
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
func (agg *dependencyAggregator) Start(closeChan chan struct{}) {
agg.closeChan = closeChan
func (agg *dependencyAggregator) Start() {

eventTime: time.Now(),
}
select {
case agg.inputChan <- event:
Copy link
Member

Choose a reason for hiding this comment

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

What is the motivation for having this done in the background instead of in the caller goroutine? Are the operations on Beam pipeline threadsafe or is this the reason for separation?

Copy link
Author

Choose a reason for hiding this comment

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

The motivation for processing spans in the background (via a separate goroutine) rather than in the caller goroutine is primarily related to performance optimization, decoupling of concerns, and ensuring thread safety when interacting with the Apache Beam pipeline

config: &cfg,
telset: telset,
dependencyWriter: dependencyWriter,
inputChan: make(chan spanEvent, 1000),
Copy link
Member

Choose a reason for hiding this comment

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

what's the motivation for making this a bound queue?

Copy link
Author

Choose a reason for hiding this comment

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

The motivation for making the inputChan a bounded queue (a buffered channel with a fixed size, e.g., 1000) is primarily to manage backpressure, control resource usage, and ensure system stability in high-throughput scenarios

Copy link
Member

Choose a reason for hiding this comment

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

The motivation for making the inputChan a bounded queue (a buffered channel with a fixed size, e.g., 1000) is primarily to manage backpressure, control resource usage, and ensure system stability in high-throughput scenarios

When a channel is unbounded, it cannot be written to unless there there is a reader waiting to consume it, so it provides a natural back pressure as the caller goroutine will be blocked and hold the remote caller. And it does not allow the queue to grow and accumulate unprocessed data while making it look like the processing was immediately successful.

Copy link
Author

@yunmaoQu yunmaoQu Feb 2, 2025

Choose a reason for hiding this comment

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

@yurishkuro Ok,i will fix it . Could the part of code is ready to be merged ?

@yunmaoQu
Copy link
Author

yunmaoQu commented Feb 4, 2025

Hi @mahadzaryab1 , would you mind taking a look at my PR when you have a moment? I'd appreciate your feedback. Thanks!

@yunmaoQu
Copy link
Author

yunmaoQu commented Feb 4, 2025

@yurishkuro I will fix the part you mentioned . Could the part of code is ready to be merged ?

@yunmaoQu
Copy link
Author

yunmaoQu commented Feb 4, 2025

Hey @mahadzaryab1 , I know you're familiar with this part of the code. Could you give my PR a look and share your thoughts? Thank you!

@mahadzaryab1
Copy link
Collaborator

Hey @mahadzaryab1 , I know you're familiar with this part of the code. Could you give my PR a look and share your thoughts? Thank you!

yeah absolutely - i'll take a look soon

@mahadzaryab1
Copy link
Collaborator

@yunmaoQu It looks like the CI is failing - are you able to fix the tests before I take a look?

@yunmaoQu
Copy link
Author

yunmaoQu commented Feb 11, 2025

@yunmaoQu It looks like the CI is failing - are you able to fix the tests before I take a look?

@mahadzaryab1 I have some unchanged files with check annotations.Could you help me with that .I think the part I focus is Done.

@yunmaoQu
Copy link
Author

@mahadzaryab1 I have some unchanged files with check annotations.Could you help me with that .I think the part I focus is Done.

@yurishkuro
Copy link
Member

@yunmaoQu the CI build is all red, please make sure it all builds cleanly.

// Writer writes spans to storage.
type Writer interface {
WriteSpan(ctx context.Context, span *model.Span) error
WriteDependencies(ctx context.Context, ts time.Time, dependencies []model.DependencyLink) error
Copy link
Member

Choose a reason for hiding this comment

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

this should be in a separate interface, spanstore.Writer is not required to support dependencies.

also it should be in the v2 storage, no reason to update v1.

Copy link

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. You may re-open it if you need more time.

@github-actions github-actions bot added the stale The issue/PR has become stale and may be auto-closed label Jul 28, 2025
timestamped := beam.ParDo(s, func(event spanEvent) beam.WindowValue {
return beam.WindowValue{
Timestamp: event.eventTime,
Windows: beam.window.IntervalWindow{Start: event.eventTime, End: event.eventTime.Add(agg.config.InactivityTimeout)},
Copy link
Contributor

Choose a reason for hiding this comment

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

The code references beam.window.IntervalWindow, but this appears to be an incorrect import path. The correct approach would be to import the window package separately and use window.IntervalWindow. Please update the import statement to include:

"github.com/apache/beam/sdks/v2/go/pkg/beam/window"

Then modify the code to use:

Windows: window.IntervalWindow{Start: event.eventTime, End: event.eventTime.Add(agg.config.InactivityTimeout)},

This will ensure proper access to the Beam windowing functionality.

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +6 to +19
import (
"context"
"sync"
"time"

"github.com/apache/beam/sdks/v2/go/pkg/beam"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"

"github.com/jaegertracing/jaeger/model"
"github.com/jaegertracing/jaeger/storage/spanstore"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

The imports section is missing required packages for the Apache Beam window and stats functionality. Please add the following imports:

"github.com/apache/beam/sdks/v2/go/pkg/beam/window"
"github.com/apache/beam/sdks/v2/go/pkg/beam/stats"

These packages are referenced in the code (e.g., beam.window.IntervalWindow and beam.stats.GroupByKey) but not imported, which will cause compilation errors.

Suggested change
import (
"context"
"sync"
"time"
"github.com/apache/beam/sdks/v2/go/pkg/beam"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/model"
"github.com/jaegertracing/jaeger/storage/spanstore"
)
import (
"context"
"sync"
"time"
"github.com/apache/beam/sdks/v2/go/pkg/beam"
"github.com/apache/beam/sdks/v2/go/pkg/beam/stats"
"github.com/apache/beam/sdks/v2/go/pkg/beam/window"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
"github.com/jaegertracing/jaeger/model"
"github.com/jaegertracing/jaeger/storage/spanstore"
)

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +132 to +139
dependencies := beam.ParDo(s, func(key pcommon.TraceID, iter func(*spanEvent) bool) []*model.DependencyLink {
spanMap := make(map[pcommon.SpanID]spanEvent)
var event *spanEvent

// Build span map
for iter(event) {
spanMap[event.span.SpanID()] = *event
}
Copy link
Contributor

Choose a reason for hiding this comment

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

There appears to be an issue with the iterator usage in this code. The pointer variable event is declared but never initialized before being passed to the iterator function. Since iter(event) expects to populate the pointer, it should be initialized first.

Consider changing:

var event *spanEvent

// Build span map
for iter(event) {
    spanMap[event.span.SpanID()] = *event
}

To:

event := new(spanEvent)

// Build span map
for iter(event) {
    spanMap[event.span.SpanID()] = *event
}

This ensures the iterator has a valid pointer to populate during each iteration.

Suggested change
dependencies := beam.ParDo(s, func(key pcommon.TraceID, iter func(*spanEvent) bool) []*model.DependencyLink {
spanMap := make(map[pcommon.SpanID]spanEvent)
var event *spanEvent
// Build span map
for iter(event) {
spanMap[event.span.SpanID()] = *event
}
dependencies := beam.ParDo(s, func(key pcommon.TraceID, iter func(*spanEvent) bool) []*model.DependencyLink {
spanMap := make(map[pcommon.SpanID]spanEvent)
event := new(spanEvent)
// Build span map
for iter(event) {
spanMap[event.span.SpanID()] = *event
}

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +191 to +196
func depMapToSlice(deps map[string]*model.DependencyLink) []*model.DependencyLink {
result := make([]*model.DependencyLink, 0, len(deps))
for _, dep := range deps {
result = append(result, dep)
}
return result
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a type mismatch between the return value of depMapToSlice() and what the WriteDependencies() interface method expects. The function currently returns []*model.DependencyLink (slice of pointers), but the interface method expects []model.DependencyLink (slice of values). This will cause compilation errors when trying to pass the result to the writer.

Consider modifying the function to return the correct type:

func depMapToSlice(deps map[string]*model.DependencyLink) []model.DependencyLink {
    result := make([]model.DependencyLink, 0, len(deps))
    for _, dep := range deps {
        result = append(result, *dep)  // Dereference the pointer
    }
    return result
}
Suggested change
func depMapToSlice(deps map[string]*model.DependencyLink) []*model.DependencyLink {
result := make([]*model.DependencyLink, 0, len(deps))
for _, dep := range deps {
result = append(result, dep)
}
return result
func depMapToSlice(deps map[string]*model.DependencyLink) []model.DependencyLink {
result := make([]model.DependencyLink, 0, len(deps))
for _, dep := range deps {
result = append(result, *dep)
}
return result
}

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

@github-actions github-actions bot removed the stale The issue/PR has become stale and may be auto-closed label Aug 4, 2025
Copy link

github-actions bot commented Oct 6, 2025

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. You may re-open it if you need more time.

@github-actions github-actions bot added the stale The issue/PR has become stale and may be auto-closed label Oct 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale The issue/PR has become stale and may be auto-closed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement in-memory Service Dependency Graph using Apache Beam

3 participants