Design your tracing stack

A tracing stack is a chain of decisions — what to instrument, what to propagate, how much to keep and by what rule, where that decision is made, how a trace gets found, and what may be derived from it — and each is right only for a given workload.

Previously

Every mechanism in the curriculum now has a price attached. The only remaining question is which ones a given workload actually needs.

Scene 16

Design your tracing stack

  1. Watch
  2. Try it
  3. Predict
  4. Capture
design the tracing system for this workloadevery check names the scene it came fromstartupShopfront at scaleseven-year complianceDESIGNING AGAINSTstartupservices3traffic200 req/shops per request4traces are readby hand, while debuggingYOUR DECISIONS6 of 61what is instrumentedSDK on 3 services, every hop2what is propagatedtraceparent on every call3how much is kept, by what rulehead, keep 100% at 200 req/s4where that decision is madein the app, at the root span5how a trace gets foundone store, index on service + op6what may be derived from itnothing derived — read by handWHAT THIS DESIGN COSTS AND BUYSspans/day at 200 req/s × 4 hops≈ 69 Mstored at ~500 B a span, 100% kept≈ 35 GB/dayX-Ray at $5 per M traces recorded≈ $86/daytiers you have to operate1, statelessTHE CHECKER4 ok · 0 warn · 0 fail✓One span per hop and traceparent on every call, so threeservices assemble into one trace instead of threedashboards that each look fine.tracing-03✓200 req/s × 4 hops ≈ 69 M spans a day, about 35 GB at~500 B a span — small enough that keeping everythingbeats reasoning about what you dropped.tracing-06✓Spans leave on a bounded queue off the request path, so abackend outage drops telemetry instead of slowingcheckout.tracing-10✓Nobody here is holding a trace id, so an index on serviceand operation is what makes a trace findable at all.tracing-13every verdict carries the scene that justifies it — cost figures worked from ~500 B a span and X-Ray's $5 per million traces recorded
What to watch for

Which of the fifteen mechanisms you have met does a given workload actually need? Here is a small one: three services, 200 requests a second, traces read by hand while debugging. A stack is already placed, and the checker on the right is green — but look at what makes each verdict trustworthy: every line names the scene it came from. Then watch a gateway tail-sampling tier get added to the design, and watch the checker's answer change without a single fact about the workload changing.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Design.check
walks the design against the workload; every verdict cites a scene
MAX_TRACE_RETENTION = 30 * DAY # X-Ray, fixed
def check(design, workload):
if workload.retention > MAX_TRACE_RETENTION:
return REFUSE(
asked_for = 'every payment, kept forever',
belongs_in = 'append-only record, by account',
)
return [
judge_chain(design, workload), # 03, 04
judge_keep(design, workload), # 06, 07, 09, 11
judge_entry(design, workload), # 10, 13, 15
]
Design.judge_chain
does one request's context survive every hop it takes
def judge_chain(design, workload):
for hop in workload.hops:
if hop.kind == 'http':
carried = design.header('traceparent') # 03
else:
# no outbound request to put a header on
carried = design.inject_into_message() # 04
if not carried:
return fail('the chain stops at this hop;'
' the next service starts a trace')
return ok()
Design.judge_keep
how much is kept, by what rule, and where that call is made
def judge_keep(design, workload):
if workload.affordable_at_full_keep: # 06
if design.sampling == 'tail':
return warn('nothing for a policy to choose')
return ok('keep 100%; deciding costs more')
out = []
if workload.must_keep('errors', 'over 2 s'): # 09
if design.sampling != 'tail':
out.append(fail('decided at the first span'))
if design.decide_at != 'gateway_pool': # 07, 11
out.append(fail('one decision, on a whole trace'))
return out or [ok()]
Design.sizing
the bill: span rate x hops x bytes x what you keep it for
SPAN_BYTES = 500 # a span on the wire
PER_MILLION_TRACES = 5.00 # X-Ray, recorded
def sizing(design, workload):
spans = workload.rps * workload.hops_per_request
ingest = spans * SPAN_BYTES # tail ships all of it
stored_per_day = ingest * design.keep_rate * 86400
bill = workload.rps * 86400 / 1e6 * PER_MILLION_TRACES
tiers = 2 if design.decide_at == 'gateway_pool' else 1
held_for = min(workload.retention, MAX_TRACE_RETENTION)
return stored_per_day, bill, tiers, held_for

Where this sits in Build a distributed tracing system (Jaeger / Zipkin style)

Scene 16 of 17, in the Ship it act — What a million traces say, then design the stack.. A tracing stack is a chain of decisions: what to instrument, what to propagate, how much to keep and by what rule, where that decision is made, and how a trace gets found. Each is right only for a given workload.

All 17 scenes in Build a distributed tracing system (Jaeger / Zipkin style) · Every curriculum

Built with Arqly
Every scene in Build a distributed tracing system (Jaeger / Zipkin style) builds on the one before it.All 17 Build a distributed tracing system (Jaeger / Zipkin style) scenes