How to Use Relational Fields: Some Nifty Use Cases

How to Use Relational Fields: Some Nifty Use Cases

7 Min. Read

We recently introduced relational fields, a new feature that allows you to query spans based on their relationship to each other within a trace. You can now query for spans where its root span, direct parent span, or any other single span in the trace has certain attributes. We currently support the following three prefixes:

root. – Identifies the root span within a trace. To find a match, any additional root. filters in your query will search through fields only in the specified root. span.

parent. – Identifies a direct parent span within a trace. To find a match, any parent. filters in your query will search through fields only in the specified parent. span, and any other filters without the prefix will search through fields in their immediate child spans.

any. – Identifies any single span within a trace. All regular filters will apply to one span within each trace, and all any. filters will apply to one span within those traces. These two spans can be any spans within the trace, no matter the relationship between the two.

This post identifies use cases that were previously impossible (or extremely difficult!) without these relational fields.

Error analysis

Before relational fields

Previously, queries considered spans in isolation: You could ask about field values on spans and aggregate them based on matching criteria, but you couldn’t use any qualifying relationships about where or how the spans appear in a trace.

For example, let’s say I’m a developer working for an e-commerce site and I want to find errors during the checkout process. I know that the root span is called /cart/checkout, which is our internal name for the start of the journey. Previous to relational fields, I would only be able to search for errors happening on the root span itself but not if it occurred elsewhere in the flow.

I might write a query like this:

Relational Fields Use Cases: Writing a query for error analysis.
Relational Fields Use Cases: A trace for error analysis.

It would find all traces outlining the checkout flow that have errors on the root span. However, it wouldn’t be able to find a trace similar to the one below with errors further in the checkout journey—for example, if an error occurred on a HTTP Post span within the trace.

Relational Fields Use Cases: A trace for error analysis.

After relational fields

With relational fields, I can now write a query that finds any error within the checkout journey.

  1. First, I’ll specify that I’m searching for spans within the checkout journey by adding a filter: root.name = /cart/checkout. This filter will now only find spans where the root span in the trace is called /cart/checkout.
  2. Next, I’ll search for any spans with errors by adding a filter: error exists.
  3. Lastly, I’ll add a group by name to see a count of the types of spans that have errored in the checkout flow.

Here’s an example of the query I wrote:

Running this query will show the following the results:

Relational Fields Use Cases: Error analysis, after relational fields.

There were 11,128 message spans and 4,955 msdemo.CheckoutService/PlaceOrder spans with errors where the root span was /cart/checkout. These would be the two places I investigate first.

There were also 3,536 root spans that errored, which returns the same count as my initial query without relational fields:

Relational Fields Use Cases: Error analysis, after relational fields.

Clicking a random trace in this new query now returns a trace where an error occurred somewhere in the middle of the checkout journey:

Performance optimization

Before relational fields

For the same e-commerce company, I now want to figure out the performance of database calls when getting shipping quotes during the checkout process. Before relational fields, I would first write a query to find the names of the database statements that are part of retrieving a shipping quote by looking for a span with name msdemo.ShippingService/GetQuote:

Relational Fields Use Cases: Performance optimization, before relational fields.

I would then input its db.statement spans into a separate query, like so:

Relational Fields Use Cases: Performance optimization, before relational fields.

After relational fields

With relational fields, I no longer need to know the exact names of the db.statement spans, just the name of the parent span that kicks them off.

  1. First, I’ll specify that I’m searching for spans within the checkout journey by adding a filter parent.name = msdemo.ShippingService/GetQuote. This filter will now only find spans where the direct parent span in the trace is called msdemo.ShippingService/GetQuote
  2. Next, I’ll search for any spans with db statements by adding a filter db.statement exists .
  3. Lastly, I’ll add a group by name to see which db statement is taking longer.
Relational Fields Use Cases: Performance optimization, after relational fields.

This returns the same query result as before, but without the extra step of querying for the exact names of the db.statements. I can now see that the INSERT shipping.quotes query takes quite a bit longer than the SELECT shipping.products query. This would be where I could first start optimizing this part of the flow.


Want to try some of this yourself?
Get started with Honeycomb for free.


Customer support

Before relational fields

Let’s imagine a customer reports errors when adding a product to cart. We know we can filter for their user_id 100701 and that we can filter for the productcatalog service. However, the user ID is only available at the start of a request—the root span—and we haven’t propagated this value to every other child span.

Without relational fields, we try to filter for errors where the user_id = 100701 and service.name = productcatalog.

Relational Fields Use Cases: Customer support, before relational fields.

This query returned no results because the user_id field is only on the root span. Our investigation would require us to first find the root spans that have user_id = 100701, and individually click through traces until we find one with the error that a customer experienced in a downstream operation.

After relational fields

With relational fields, we can now search for traces where the root span has the user_id of the customer and where the productcatalog service is mentioned somewhere in the trace. We can then search for any spans in this trace with errors.

  1. First, I’ll specify that I’m searching for spans where root.app.user_id = 100701. This filter will now only find traces where the root span has this user_id.
  2. Next, I’ll specify that somewhere in the trace, we should see at least one span in productcatalog by filtering for any.service.name = productcatalog .
  3. Lastly, I’ll search for spans where an error exists and group by the span name.

Clicking into one of the traces from this result, I can see span events on a erroring span in the productcatalog service. In its status_message field, I can see the error no product with ID BREAKMENOW.

I can quickly escalate this to my team and let them know a placeholder ID for the product was pushed to production and is now blocking customers from adding the product to cart. We can switch out the placeholder ID for the real product ID (and offer the customer a discount when they retry this purchase). Critically, to perform this kind of analysis quickly in the future, I no longer need to worry about propagating values like user_id across all spans in a trace to find errors that a user experienced. I can simply use relational fields!

Conclusion

There are many workflows that used to be difficult or nearly impossible without relational fields. With the power to specify relationships within a trace, you can now query across your entire trace to find spans of interest. Learn more in our docs.

Don’t forget to share!

Related posts