-
Notifications
You must be signed in to change notification settings - Fork 132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: SpanContext should be immutable #106
Comments
I'm in favor of setting it at the root only (similar to what we do in zipkin for trace identifiers, sampled state etc). This eliminates the interesting, but tedious to support questions on what about branching, after finish etc. This keeps the api surface smaller without precluding it from being added later. |
+1 for what @adriancole said. I think misuse, abuse, overuse and all the negative *use can be much smaller if we limit this to the root :) |
I don't feel strongly about this, but I do like the idea of Spans This separation would especially be useful in implementations where On Thu, Jul 14, 2016 at 09:10:53PM -0700, bhs wrote:
Dominik Honnef |
How about keeping interface Span {
SpanContext contextOf(String baggageKey, String baggageValue);
SpanContext contextOf(Map<String, String> baggageItems);
SpanContext context();
}
interface SpanContext extends Iterable<Map.Entry<String, String>> {
String getBaggageItem(String baggageKey);
} The Since adding a baggage item without using the resulting context to link a descendant span or inject to a carrier does not make sense, I don't think this API would burden useful code. |
I have a slightly different idea... let's make baggage only settable at Span initialization time, at least until a use case forces some other behavior. I.e.,
And of course similar things in other languages. This would eliminate some uncertainty about when baggage changes are visible. Given that baggage-setting is supposed to be an infrequent activity, I don't mind making the write API cumbersome. If this LG to people, I am happy to (quickly) make the changes to the various APIs. |
Also: @adriancole and @marcingrzejszczak, re
As a general guiding principle, I don't like making tracing APIs that depend on being at the root of the trace. E.g., server-side devs often think they're the "root", then eventually realize they need to incorporate the mobile/web RPC client. And then the mobile/web RPC client realizes that the actual UI button-press is the root. And then the actual UI button-press realizes that the UX task that's being performed ("book a hotel", "follow someone new", "buy that necklace") is the true root. I.e., one programmer's root is another's child. |
Hmm... to make it as simple as possible. If the baggage contains data == it has already been in the root. Wouldn't that be some acceptable solution? |
@marcingrzejszczak consider
If the storage system is relying on baggage being set in Frontend Server, what do we do when we hook up the web client? Suddenly that frontend server baggage is no longer available. This is not an artificial example... The primary motivating usecases for baggage are security and resource accounting things that likely cannot be safely set in an end-user client. I appreciate that the mental model is simpler if baggage can only be set at the root. Unfortunately I also think that constraint would hobble the feature from a value standpoint. |
On Fri, Jul 15, 2016 at 08:54:34AM -0700, bhs wrote:
I don't feel comfortable about that. I'd expect most baggage to be I do want to pose a more general question, though: why is baggage part |
@bensigelman Wouldn't making baggage settable at span creation time only make it harder to access the relevant detail you want to set as the baggage? For example, if you want to set SpanContext incomingContext = serverTracer.extract(httpCarrier(incomingReq));
try (Span span = serverTracer.buildSpan("query").asChildOf(incomingContext).start()) {
QueryDetails query = parse(incomingReq);
String accountId = query.accountId(); // now what?
} I think this may be a common occurrence; the surrounding context to figure out the baggage item likely doesn't exist until you can do some relatively expensive processing. To overcome the problem, you may have to introduce lightweight parsing to extract just enough of the context out of a request (which may require you to rework your app protocol so that anything that may end up as trace baggage is easily accessible from a raw request), or you could make peace with the fact that request parsing will not be attributed to a useful tracing context. |
The mental image I have of tags and baggage items in OT is that tags belong on the vertices/spans while baggage flows through edges/references. Coupling the addition of new baggage to creating a new reference makes sense to me in that context. Maybe due to this mental model, I will also admit that On the other hand, I wouldn't want to open up another long vocabulary discussion when most of the group is comfortable with the current naming; if that's the case, feel free to ignore the musings in the above paragraph. :) |
@tinkerware we discussed a bunch of possible names for Re your other comment:
It does make it a little harder to set baggage, but it's something one could work around.
I.e., you can still measure the cost of parsing headers and set the baggage item as long as you don't mind introducing a span that precisely measures just the cost of parsing the headers. Seems like a reasonable thing to me, and perhaps even desirable from a precision standpoint. |
@dominikh, re:
http://opentracing.io/spec/#baggage has a little bit of information... you can read (much) more in the Pivot Tracing paper available via http://pivottracing.io/ ... OT does not yet support most/all of that functionality, but I think the basic storage accounting use case as well as some security auditing can be accomplished with the current baggage mechanism, and I am certain that those use cases are valuable at larger organizations. (Google has been using the dapper code path to do this sort of thing for many years now) |
@tinkerware most of the SpanContext naming conversation happened as part of opentracing/opentracing-go#82 |
What is the need for making SpanContext immutable in the first place? Span is not immutable, and SpanContext is just a mini-me of the Span that can be packaged over the wire. |
@yurishkuro two reasons:
|
That's not the case in several implementations I've seen, where the mutex in SpanContext is the only mutex, Span just shares it.
While I agree that it's odd, the behavior of doing so is still clearly defined. For example, we did intend that FollowsFrom spans could be created after the original span was finished. So if baggage is added after finish, they will inherit that baggage. I view SpanContext as a projection of Span state at a given point in time. Baggage is a part of that projection in as much as it needs to be passed to the Inject method(). And if the state is dynamic, the projection can be live/dynamic too. In practice, baggage is not the only dynamic part of the context, most tracers implement sampling, which must be propagated, so if someone does And there are certainly use cases for modifying baggage after span creation. For example, vector clocks can be returned by one dowstream call and propagated to future downstream calls, all being children of the same parent span. I am not in favor of restricting flexibility, without stronger justifications. There's also this repo http://github.com/openctx, it's trying to solve the problem of DCP (and baggage) outside of tracing (tracing become a plugin). What's interesting about it is that Go version takes the immutable approach (create child context to add baggage), while Node uses mutable context. |
Ex in zipkin, there's an edge case (no pun intended) for this which does Ex. client sends all of the trace baggage except the sampled bit There's heavy disclaimers about how messy it would be if the sampled Long story short is that I suppose a tier can add a key not seen yet, Generally, this sort of thing is a tough goal in OT as it tries to be |
A few more thoughts about this:
|
On Sat, Jul 16, 2016 at 02:36:17PM -0700, bhs wrote:
What about the peer tags? Dominik Honnef |
@dominikh peer tags are definitely per-span... but if we're talking about sampling, ideally it's trace-wide (or there's not much point). |
First I think there is a slight ambiguity of the word root here. I proposed the Span to be the root in the aggregate pattern. I read some comments like the trace should be the root which is not what I meant. Every span must have the possibility to add more baggage. In the aggregate pattern everything must be passed through the root object. It is okey to get immutable objects of inner parts of the aggregate (like the SpanContext out of the Span), but not mutable object, since that opens the possibility to bypass the root and create side effects. What I talk about here is the public API for the span and spanContext interfaces. For implementations it is still possible that the span can have internal access to a set method on the spanContext, and just pass the setBaggage parameters on to the context. This is actually the more flexible, less restricting solution. If we expose setBaggage on the spanContext, we also force the spanContext to be mutable, otherwise there is no way for the span to know about the changes. By moving the setBaggage method to the span we give the implementors the possibility to choose if the spanContext should be mutable or immutable.
This is just fine. setTag is already a method on the span (the root). The span can choose to propagate the tag into the context if it likes. No evil side effects here.
Yes! Could not agree more. And this should be the reason why Span exposes the SpanContext, to pass it to the Inject method, not to let the user manipulate it. I'm not sure if setting baggage only at span creation is a good idea. I think it limits the use cases for baggage. I like to have the possibility to add baggage after creation, just not directly on the context. |
I agree, I was also thinking along the same lines. I wouldn't mind moving baggage back to the span, but I do like the ability to read baggage off the SpanContext, especially when that context has just been extracted from the wire. It will be a bit asymmetrical, Span is R/W, SpanContext is R-only, but that's the nature of the projection. |
If we think it makes sense to allow for baggage additions at Span start/creation time, I am merely suggesting that we begin with that API and see how things evolve. Baggage is still an experimental feature and we do not yet have meaningful data about how it will be used in the long run. For that reason, I like the idea of removing the Thoughts? |
I have an open PR to integrate OpenTracing with TChannel, which explicitly relies on the ability to add baggage to a span created elsewhere. It's not the most orthodox use of OpenTracing API, but it will be completely broken (as in severely undercut the functionality in that PR) if baggage cannot be added to existing span. |
@yurishkuro interesting, thank you for sending the pointer. I glanced at the PR. In Anyway, "good" to know that we already have a strong use case for setting baggage items on already-started Revised strawman: supported baggage methods would be
Nothing more, nothing less. Thoughts? |
@bensigelman don't want to muddy this thread w/ tchannel discussion, just want to mention that what you're saying is possible, but makes the prevalent case of thrift/json encoding unnecessarily complex by forcing it through the dual-span approach. On the proposal, if I put my OOP hat, the following would make more sense to me (NB: dynamic languages say boo-o-o): type BaggageWriter interface {
SetBaggageItem(key, val)
}
type BaggageReader interface {
BaggageItem(key)
ForeachBaggageItem(callback)
}
type Span interface {
BaggageReader
BaggageWriter
// ...
}
type SpanContext interface {
BaggageReader
} This way the user-facing baggage API is primarily on the Span. |
The excellent workshop that @adriancole organized has forced me into navel-gazing territory vis-a-vis this issue. Namely, the https://github.com/openctx/openctx-go work that @kriskowal recently kicked off and the ideas that @rfonseca and @JonathanMace shared made me ponder why Spans are a "thing" at all. (As opposed to just logging structured data and causality / reference information against a context or baggage entity) The short version of my thinking is that the Span concept exists as an optimization: both for performance and for instrumentation clarity (the "long version of my thinking" includes justification for this assertion :). If we proceed with this change, what we are really saying (in so many words) is that a Span is the only way you mutate and/or record data about a SpanContext. Two paths forward:
Thoughts? |
I'm currently on vacation and unfortunately missed the workshop. @bensigelman I like your proposal above to have get and set baggage on the Span and a read iterator on the SpanContext. It solves the problems I have with the existing mutable SpanContext. @bensigelman I don't really see what you mean in your last comment, and really like to hear "the long version". Do you suggest we should skip Span and just have a Context? Or is it just a naming problem. To me, a Span is a kind of context, for legacy reasons called a Span. The SpanContext is then just the part of the span/context that is propagated to children spans. That we have only a single root object (the Span) and everything has to be changed through that root is to me just good API design. Things tend to be messy if objects reference other mutable objects. I'm opened and interested in further discussions. |
my $0.02:
In summary, I am 👍 for moving get/set baggage back to the Span and only exposing the iterator on the context |
I just tried to follow this discussion to its start, and since I don't have enough "context", I will not get into details on Span vs SpanContext vis-a-vis baggage. I'd like to offer a couple of comments related to the tracing plane proposal, which is really an attempt to provide a layered architecture to this space. Please let me know if there is a better place to discuss this than this thread. (Aside) What is this tracing plane proposal? Essentially a division of the problem into 3 layers:
Note that this is an idealized architecture. The last thing we want is to be the what the OSI Layer Model became for networking. (If you know what that is, you get my point. If you don't, well, that is my point! :) -- this was a 7-layer complete model of what layering should be in networking, and the more pragmatic 4-layer TCP/IP model gained all the adoption). The most important and relevant aspect here, IMO, is the reason for all of this: how you propagate metadata does not vary much among multiple possible uses, what you propagate does. So it's important to not couple the two, for the future. Baggage as an underlying "transport" is flexible enough that you can implement all sorts of apps above, including OT/Zipkin/Dapper/HTrace, but also security, deadlines, Pivot Tracing, Retro, etc. (I also fully appreciate this is the reason baggage is in the OT spec!) Baggage (as an underlying transport for metadata) does not presuppose an events vs spans model. Baggage does not say anything about data that has to be in-band vs data that has to be out-of-band. For example, the original X-Trace has fixed-size metadata that is just a task-id and an event-id, everything else, including the binding between parents and children, multiple parents for an event, and data about the event, was logged out of band. You would construct a "report" for an event in X-Trace, add all this data to it, and then send it out of band. This is perfectly implementable using baggage. PivotTracing does have query data that is added to the baggage, but this is one use, and is not mandated. One of the drawbacks of the 3-layer architecture, as @bensigelman pointed out in the workshop, is that it is has a high cognitive load, too many things to specify, code, and deploy, perhaps. One perfectly fine position to take, for example, is to say nothing about the division of layers 1 and 2, maybe implement them together, and this is pretty much what OpenCtx is trying to do. There are some small differences between what we've been thinking at Brown and OpenCtx, mostly having to do with how to express the behavior of joining two pieces of baggage, but this can all be abstracted away and hidden behind transparent APIs and some well defined default behaviors. A couple of questions as food for thought: 1. Assuming that we have a system that implements OpenCtx and/or the Baggage layer from Brown's proposal, how hard would it be to implement the OT interface on top of it? 2. What would have to change (perhaps simplified) in the OT spec, if these concerns of moving the metadata around, serializing, injecting, extracting, etc, were taken care of by something like OpenCtx/Baggage? |
The insight from Brown, that baggage can be lazily accumulated and merged For OpenCtx, we would have to decide upon a baggage encoding, suitable for On Mon, Aug 1, 2016 at 11:13 AM Yuri Shkuro, PhD notifications@github.com
|
Per opentracing/opentracing.io#106 (same id, ironically)
As far as I've seen this has been updated everywhere except Objective-C. However, the public spec still has the methods on |
@cwe1ss good point, I will handle it before closing this issue. |
We're all set! Closing. |
@dawallin made an offhand remark on opentracing/opentracing-java#32 (comment):
As I said on that thread:
We should discuss this here... I'm cc'ing a few folks from that PR in case they have thoughts: @adriancole @yurishkuro @michaelsembwever @tinkerware; as well as @dominikh who has been neck-deep in OpenTracing implementation details lately. :)
The text was updated successfully, but these errors were encountered: