Skip to content

Add the ability to store/retrieve incomplete/partial spans #729

Description

@vprithvi

Summary

Allow clients to export partial spans, to support two use cases:

  • Flush a long running span before it is finished, in case the process crashes before finishing it
  • Enrich existing span with information from other sources, e.g. to record log events not captured via tracing SDK

Details

For the first version of this feature, we shall assume the following

  • clients can flush and report partial spans; but not span deltas
  • duration is monotonically increasing
  • the final span has the longest duration

For e.g., a client can do the following
time = 1s
Report Span[traceID=1, spanID=2, duration=10s, operationName="someOperation"]

time = 2s
Report Span[traceID=1, spanID=2, duration=20s, operationName="someOperation"]

time = 3s
Report Span[traceID=1, spanID=2, duration=40s, operationName="someOperation", tags=...]

To support this, the backend would need the ability to resolve merge conflicts on spans. For V1, this means simply selecting the longest span.

On Cassandra, the jaeger-collector uses the model spanhash, guaranteeing that all partial spans are stored:

spanHash, _ := model.HashCode(span)

jaeger/model/span.go

Lines 63 to 66 in fcbd210

func (s *Span) Hash(w io.Writer) (err error) {
// gob is not the most efficient way, but it ensures we don't miss any fields.
// See BenchmarkSpanHash in span_test.go
enc := gob.NewEncoder(w)

On ElasticSearch, we use the index api, which performs upserts.

func (s *SpanWriter) writeSpan(indexName string, jsonSpan *jModel.Span) {
elasticSpan := Span{Span: jsonSpan, StartTimeMillis: jsonSpan.StartTime / 1000} // Microseconds to milliseconds
s.client.Index().Index(indexName).Type(spanType).BodyJson(&elasticSpan).Add()

To make matters more interesting, note that Jaeger supports storing Zipkin spans that share the same spanID for client and server spans. Jaeger adjusts these spanIDs during query time as seen here:

// SpanIDDeduper returns an adjuster that changes span ids for server
// spans (i.e. spans with tag: span.kind == server) if there is another
// client span that shares the same span ID. This is needed to deal with
// Zipkin-style clients that reuse the same span ID for both client and server
// side of an RPC call. Jaeger UI expects all spans to have unique IDs.
//
// This adjuster never returns any errors. Instead it records any issues
// it encounters in Span.Warnings.
func SpanIDDeduper() Adjuster {

Another point to note is that jaeger-collector makes no guarantee that spans are stored in the order that they are received.

Enhancements

  1. Communicate that jaeger-query is serving incomplete spans to the user. One approach is to do something similar to Icon / demarcation for client spans with no responding server span jaeger-ui#132. We might have to enhance the Span model to store this information
  2. Allow for jaeger-clients to report that the span it is sending is the final span. This allows us to remove the assumption about monotonically increasing duration.
  3. Add support for span deltas, which might allow for clients to reduce state maintained. (Might be at odds with the previous point)
  4. Enhance Span model to store the lineage of a span (whether it was generated from a Zipkin or Jaeger span), this allows for more robust merging behavior

For more context on use cases and prior discussion see jaegertracing/jaeger-client-java#231

Similar tickets:

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedFeatures that maintainers are willing to accept but do not have cycles to implement

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions