DEV Community

Cover image for Mastering Trace Analysis with Span Links using openTelemetry and Signoz (A Practical Guide,Part 2)
Abdulsalaam Noibi
Abdulsalaam Noibi

Posted on

Mastering Trace Analysis with Span Links using openTelemetry and Signoz (A Practical Guide,Part 2)

In the previous tutorial, we learnt how to use span links to track interactions within distributed systems

In this tutorial, we will look at how to implement Best Practices for Using Span Links and Advanced Use Cases

Best Practices for Using Span Links in OpenTelemetry

When dealing with complex distributed systems, choosing the right tracing strategy is essential for maintaining clarity and performance.

In OpenTelemetry, the two primary tools at your disposal are parent-child relationships and span links. Let’s explore when and how to use span links effectively, especially in comparison to the more common parent-child relationships.

1. When to Use Span Links and Parent-Child Relationships

Understanding when to use span links as opposed to parent-child traces is crucial for correctly mapping how your services communicate.

Parent-Child Relationship: The Standard Tracing Model

Parent-child relationships in tracing are straightforward. If one service calls another, the trace creates a direct parent-child link between the two spans. The child span is dependent on the parent span, clearly showing the flow of operations.

This model works well in synchronous operations, where one task directly triggers another, and they follow a linear progression, such as:

  • A user request triggers a function.
  • The function calls a database.
  • The database returns a response.
  • Each of these actions would be a child span of the previous one, forming a neat, sequential trace.

Span Links

In real-world systems, especially those using microservices or asynchronous processes, not all operations follow this neat, hierarchical flow. This is where span links become valuable.

A span link allows you to connect two spans that may not follow a direct cause-and-effect pattern. For example:

Asynchronous Tasks: A message queue may send a request to a processing service, but you might also want to connect that request to the original service that triggered it.

Batch Jobs: You may have a system that processes data in batches, where multiple child jobs are linked back to a single trigger event, but these jobs don’t execute sequentially.

Processes Where Span Links Add Value:

Decoupled or Asynchronous Systems:
Where one process kicks off another, but there’s no direct call.

Multiple Parents: If multiple processes contribute to one result (e.g., data from several services aggregates into one report), span links allow you to connect all related spans.

Correlated Events: Span links are ideal when you need to associate spans from different traces, such as when a failure in one service causes an error in another indirectly.

Processes that makes Span Links Redundant:

Synchronous Operations: If the relationship between tasks is direct and synchronous, span links can clutter your trace visualization without adding real value. In this case, stick to parent-child relationships for simplicity.

Use Sampling Strategies with Span Links to optimize Tracing Performance:

In high-traffic systems, not every span or link needs to be captured. Sampling is a strategy where only a portion of the traces are recorded, ensuring you capture enough data for analysis without overwhelming your system.

Head-Based Sampling: This captures traces at the entry point (head) of your system. You can apply this to key services, ensuring span links are only created for high-priority or important traces.

Tail-Based Sampling: This samples traces based on the outcome, such as capturing only traces that result in errors. You can use this to ensure span links are used in cases that are most likely to need deep investigation, such as failures.

2. Naming and Structuring Span Links for Clarity

Good naming conventions and structured traces are important in order to have perfect observability data, especially when span links are involved.The name of a span should clearly describe what it represents. This becomes more important when using span links, as the relationship between spans is not always visually obvious.

Consistent Naming Conventions:

Use a consistent pattern for span names, such as including the service name, function, or action. For example, a span for a payment processing service might be named payment-service.processPayment.

Indicate the Role of Linked Spans:

In your span names, indicate the role of the linked span if relevant. For example, user-authentication.request could be linked to session-creation.init, making the connection between them clear.

Group Related Spans: group spans logically For instance, if multiple microservices contribute to one larger process, ensure that the span links and naming help identify which service is responsible for each part.

Document Link Reasons: If possible, document why a span link exists, either in the trace itself (via metadata) or in your documentation. This can be as simple as a brief comment in the tracing code explaining the relationship between two spans.

Advanced Use Cases Error Tracking for using Span Links in your Applications

How to Use Span Links to Trace Error Flows Between Services

Imagine you're managing a complex web application with numerous microservices,each microservice is responsible for a different part of the user experience.

A user might place an order, which triggers a payment service, an inventory service, and a shipping service. If an error occurs somewhere in this chain, it’s crucial to know where it happened and how it impacted other services. This is where span links come in.

Span links allow you to connect traces that aren't in a direct parent-child relationship, but still have contextual relevance.using span links for error tracking, you can correlate the error in one service with the subsequent impact on other services, even if they don’t share a direct relationship.

Use Case: Let’s say your payment service encounters an error while trying to process a transaction, and this failure indirectly impacts the shipping service. Using a span link, you can create a relationship between the error span from the payment service and the span of the shipping service that detected the issue.

This helps you visualize the flow of the error across services and understand its ripple effects.

Code Example for Capturing and Linking Error Spans Across Microservices

Let’s look at how you might capture these errors using OpenTelemetry and create span links between them. Here’s a simple example using Python:

from opentelemetry import trace

# Initialize tracer
tracer = trace.get_tracer("order-service")

# Create a span in the payment service
with tracer.start_as_current_span("payment-processing") as payment_span:
    try:
        # Simulate a payment process that raises an error
        process_payment()
    except Exception as e:
        payment_span.record_exception(e)
        payment_span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))

        # Capture the error trace and create a span link
        error_link = trace.Link(payment_span.get_span_context())

# Now in the shipping service, you can link this error trace
with tracer.start_as_current_span("shipping-service", links=[error_link]) as shipping_span:
    # Handle the impact of the payment error here
    process_shipping()

Enter fullscreen mode Exit fullscreen mode

Capturing Error using L<br>
Span Links

Explanation of the above code snippet

The payment-processing span captures the error when the payment fails.
A span link (error_link) is created using the context of the payment-processing span.

This link is then added to the shipping-service span, allowing you to trace how the payment error affects the shipping process.

You can use tools like SigNoz to visualize these errors, making it much simpler to identify the root cause of issues.

Implementing Span Links in Complex Microservice Architectures

Real-world Use Case: Using Span Links to Track Customer Interactions Across a Multi-Service Architecture

Let’s take a real-world scenario. Imagine an e-commerce platform where customer actions, like placing an order, are handled by several services: Order Service, Inventory Service, Payment Service, and Shipping Service.

A user placing a single order can generate multiple spans, one for each service.

Now, these spans are usually arranged in a parent-child relationship, where the Order Service might be the parent of the Payment Service and so forth. But what if you want to track a more complex relationship?

For example, if the Inventory Service independently checks stock levels after a payment confirmation, it’s not a direct child of the Payment Service. A span link allows you to connect these services directly, creating a more accurate picture of how your services interact.

Why Span Links is important in Complex Architectures

Span links give you the flexibility to capture these non-linear interactions, providing a comprehensive view of user actions that span across services. This is especially useful for troubleshooting user experiences, like a delayed shipment due to an inventory check.

How Span Links Enhance Observability in Serverless or Event-Driven Systems

In serverless or event-driven systems, services often interact in a decoupled manner events trigger actions without the services having direct knowledge of one another.

For example, an event from a Payment Service might trigger an Inventory Update Service through an event bus. Since these services don’t have a parent-child relationship, tracing them with traditional methods can be challenging.

How To Use Span Links for Serverless

Span links can act as the glue between these disjointed services. When an event is generated from one service and consumed by another, you can create a span link that connects the original event's span with the consuming service’s span.

This way, even if your serverless functions are running independently, you can still get the full story of an interaction.

Example: Let’s say your Payment Service sends a message to a queue after processing a payment, and this message triggers a Stock Update Function in a serverless architecture.

Here is a code snippet on how you could link these spans

# In the Payment Service
payment_span = tracer.start_span("payment-succeeded")
# Send message to queue with payment_span's context

# In the Stock Update Function triggered by the message
stock_update_span = tracer.start_span(
    "stock-update",
    links=[trace.Link(payment_span.get_span_context())]
)

Enter fullscreen mode Exit fullscreen mode

Tracing the flow of payment Processing

With this setup, you can trace the flow from the payment processing to the stock update, even though they operate asynchronously.

When visualized, it becomes clear how different parts of your serverless application interact, improving your ability to diagnose bottlenecks or unexpected delays.

Why is this approach important for Observability

Traditional monitoring might show you that a stock update was slow, but with span links, you can trace that delay back to the specific payment event that triggered it.

This level of insight is invaluable for optimizing your system and ensuring a smooth user experience.

Recap of Key Learnings:

Span links are a powerful underutilized feature of OpenTelemetry that can significantly enhance trace correlation in distributed systems.

But what exactly does that mean, and why should you care?

Imagine your application as a network of different services and processes, all communicating and working together to fulfill user requests.you will often encounter scenarios where a straightforward parent-child relationship between traces doesn’t quite capture the complexity of what’s happening.

For instance, what if a background job is processing events triggered by a user action, or multiple services are working together asynchronously? This is where span links comes in to solve the challenge easily.

So, what are the benefits of using span links?

Relating Spans Beyond Parent-Child Constraints:

Span links allow you to connect traces across services without being bound by the typical hierarchical structure of parent and child spans.

This is particularly useful when you want to relate events that occur concurrently or share a common context but don’t have a direct parent-child relationship. For example, linking a trace from a user-facing service to a background process can give you a more holistic view of how user actions impact system performance.

It helps to improve Debugging and Troubleshooting:

With span links, you gain a richer perspective on how different services interact, especially during complex workflows. By seeing which spans are related through links, you can identify bottlenecks, error patterns, or performance issues that might be difficult to spot otherwise. This makes span links a powerful tool for debugging issues that span multiple services.

It provides Better Visibility in Asynchronous Systems:

For applications that rely on asynchronous processing, such as those using message queues or event-driven architectures, span links are invaluable.

They allow you to trace the lifecycle of a task or message as it flows through different services. This can help you understand the impact of a single event across your entire system, making it easier to optimize and refine your processes.

In short, span links allow you to create a more connected and meaningful picture of your application’s behavior, leading to better observability and a deeper understanding of how your distributed systems operate.

By leveraging span links effectively, you can enhance trace correlation, making troubleshooting faster and providing a more complete view of your system's performance.

Links to Relevant OpenTelemetry Documentation for Span Links

For those looking to go deeper into the official guidance on span links and related concepts, the following resources will be valuable for your research:

OpenTelemetry Span Links Documentation

This is the go-to reference for understanding how to create and manage span links. It covers the API specifications for linking spans, with examples in various supported programming languages. It’s a great starting point for understanding the technical details of how span links work under the hood.

OpenTelemetry Context Propagation

Understanding context propagation is key to making the most of span links, and this documentation provides a thorough overview of how context is managed across traces. It’s especially helpful if you’re looking to ensure consistency in your tracing data across distributed services.

OpenTelemetry Sampling Strategies

When implementing span links, it's crucial to know how sampling affects your traces. This section of the documentation provides detailed guidance on how to configure different sampling strategies, helping you strike the right balance between data granularity and performance.

These links are valuable resources for both reference and practical application, making them essential for anyone serious about mastering OpenTelemetry's tracing capabilities. Bookmark these resources and use them as a guide as you build out more complex observability setups.

If you have questions or further explanation, kindly share them in the comments sections.

Top comments (0)