Your data. Anywhere you go.

New Relic for iOS or Android


Download on the App Store    Android App on Google play


New Relic Insights App for iOS


Download on the App Store


Learn more

Close icon

Multiple goroutines in a transaction

go-agent

#1

The documentation says that a single transaction should only be used in a single goroutine. In our application, though, we are using a common pattern where multiple external services are called in parallel using multiple goroutines. We’d like to be able to use segments to trace these calls. The obvious solution would be to start and end the segments in the main goroutine but that way we can’t access http.Request and http.Response structs that are supposedly the preferred way to use the ExternalSegment API. Is there any solution? Or is not using transactions from multiple goroutines just a suggestion and not a hard limit?

Thanks for any ideas!


#2

What’s more, I’ve just realized that according to agent’s guide, I can’t even have two non-nested segments running in parallel on a single goroutine, because I can’t guarantee which one will end first and they will nest automatically, as specified in this secton: “Segments may be nested. The segment being ended must be the most recently started segment.”


#3

Hi robert4

I recommend the following:

  • In the main routine, put a single segment around all the external calls.

  • In each goroutine that is doing an external call, start a new transaction and
    wrap the external call in an external segment.

Here is an example (rough untested code):

func getURL(url string, wg *sync.WaitGroup) {
  defer wg.Done()

  txn := app.StartTransaction("externalCall", nil, nil)
  defer txn.End()

  s := newrelic.ExternalSegment{
    StartTime: newrelic.StartSegmentNow(txn),
    URL:       url,
  }
  http.Get(url)
  s.End()
}

func handler(w http.ResponseWriter, r *http.Request) {
  var wg sync.WaitGroup
  txn, _ := w.(newrelic.Transaction)
  urls := []string{"example.com"}

  s := newrelic.StartSegment(txn, "externalCalls")
  for _, url := range urls {
    wg.Add(1)
    getURL(url, &wg)
  }
  s.End()
}

The restrictions of “non overlapping segments”, and “only use the transaction in
a single goroutine” exist to ensure that the bands in our stacked area charts
sum to the response time. The idea is that the “exclusive time” of each segment
(the duration minus the duration of the nested “children” segments) in a
transaction should add to the response time. If these restrictions are violated
you may get weird/unreliable charts and data.

Will


#4

That sounds like a good idea, I’ll try it. Thanks a lot!


#5

Hi Will, How do you suggest passing a Transaction around in a context, if we can’t have concurrently running segments?
I thought about wrapping the starting/ending of segments but can’t come up any information that would tell me they were executing concurrently. If I could then all but one of the segments could be not ended and an error logged so we know where we need to create explicit transactions. It looks like the agent is protected with mutexes so it sounds like at the worst the times/graphs get messed up in the UI?


#6

Hi,

I have been looking into the new relic agent for go and came across the fact that transactions cannot span goroutines (or have overlapping non nested segments) which hit me as very strange, given the fact that doing this kind of thing is one of the major selling points of go (and having used opentracing/zipkin which handles this just fine).

I have a few of questions:

  1. In your proposed solution, is there any way to link the two transaction together when looking at the new relic interface?
  2. Is this a limitation of CAT only, or will it be overcome in the new Distributed Tracing era?
  3. Is this a limitation just for the go implementation, or is this a hard limitation within all of new relic?

Thanks,
Chris


#7

Hi cgilling

  • I recommend the distributed tracing Transaction.CreateDistributedTracePayload and Transaction.AcceptDistributedTracePayload methods to link transactions in different goroutines together.

  • This is not a hard limitation across all agents: async support varies between agents.


#8

Thanks for the suggestion, I gave it a try and it seems to work out fairly well in the distributed tracing view. Its unfortunate that it requires creation of extra spans and transactions to get this to work. I’m currently trying to explore if there are ways to make this easier through helper functions and the like, but haven’t hit upon anything as smooth as I would like quite yet.