open atlas
↑ Back to track
AWS, hands-on AWS · 05 · 02

Distributed tracing and event-driven plumbing: X-Ray and EventBridge

Metrics and logs tell you a box is sick; distributed tracing tells you which hop is slow, and EventBridge decouples the hops. Sample traces and bound log cardinality or observability bills you twice.

AWS Senior ◷ 20 min
Level
FoundationsJuniorMiddleSenior

A checkout API starts answering in 1.8 seconds instead of 200 ms. The dashboards are maddeningly green: CPU normal, memory normal, every service’s p99 “fine.” The on-call grep through five services’ logs for an hour, find nothing conclusive, and start guessing — restart the cart service, bounce the cache, blame the database. The real cause was a payment-provider SDK that had quietly added a synchronous tax-lookup call to a fourth service, and that service was waiting on a cross-region DynamoDB read. No single log line said “I am the slow one,” because no single service was slow on its own — the latency lived in the edges between services. The team had metrics and logs, the first two pillars of observability. What they were missing was the third: a distributed trace that draws the whole request as one timeline and points at the exact hop where the milliseconds go. They turned on X-Ray, and the next incident took four minutes instead of an hour.

Tracing: the third pillar, and why logs alone fail

Metrics and logs are per-component. A metric is a number from one box; a log line is one event on one box. Neither knows that this request to checkout fanned out into a call to cart, then payment, then a tax service, then DynamoDB — and that the 1.6 seconds of extra latency was spent waiting on the tax hop. To answer “where in the call chain did the time go?” you need a record that spans services and stitches them into one timeline. That is distributed tracing.

AWS X-Ray models a request as a trace: the collection of all work done to serve one request, identified by a single trace ID. Each service contributes a segment — a document recording that service’s name, the request and response, start and end times, and any errors or faults. Within a segment, every downstream call your code makes is a subsegment: a call to DynamoDB, an external HTTP API, or a SQL query, each with its own timing. For a downstream that doesn’t trace itself (DynamoDB doesn’t emit segments), X-Ray builds an inferred segment from your subsegment, so the dependency still shows up on the map. This is the structural difference from logs: a trace is a tree of segments and subsegments, not a flat stream of lines, and the tree is exactly the shape of your call graph.

The glue that ties segments across process boundaries is a propagated header: X-Amzn-Trace-Id. The first X-Ray-integrated service a request hits stamps it with a root trace ID and a sampling decision, and every instrumented hop forwards it downstream, adding its own parent segment ID so X-Ray can reconstruct parent/child edges.

X-Amzn-Trace-Id: Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1

X-Ray rolls every service’s segments for a trace ID into a service map (service graph): nodes for each service, edges for each call between them, each edge annotated with latency and error rate. The map is what turns “checkout is slow” into “the checkout→tax edge is at 1.6 s p99 and 0% errors” — a thing logs structurally cannot show you, because a log line lives inside one node and knows nothing about the edge.

Sampling: you trade completeness for cost on purpose

Tracing every request is expensive twice over: the per-trace bill and the latency the SDK adds collecting and shipping segments. So X-Ray samples. The default rule is a reservoir plus a rate: record the first request each second, then 5% of everything beyond that. The reservoir guarantees you always capture a baseline of traces even at low traffic; the 5% rate keeps a high-volume endpoint from recording millions of near-identical traces. You configure rules per service or request property — disable sampling (trace 100%) for state-changing or payment paths where every trace matters, and sample health checks and background polling at a low rate because they’re noise.

The cost is real, and it is the most common observability failure mode. X-Ray bills per trace recorded and, separately, per trace retrieved or scanned. As illustrative figures from the CloudWatch pricing page (US East / N. Virginia — prices are region-dependent, always check your region): traces recorded cost about $5.00 per million ($0.000005 each) beyond a free tier of 100,000 per month, and retrieved/scanned traces about $0.50 per million beyond 1,000,000 free. Those numbers look trivial until someone sets sampling to 100% on a path doing 5,000 requests/second: that is ~13 billion traces a month, roughly $65,000 in recording alone — the classic “we turned on full tracing in prod and got a five-figure surprise” story. Sampling is not a quality compromise to apologize for; it is the deliberate trade that makes tracing affordable.

EventBridge: decouple the hops you just traced

The tax-lookup incident had a second lesson: that synchronous call should never have been synchronous. When the payment service must call the tax service inline and wait, the two are tightly coupled — the slow one drags the fast one, and a deploy to either can break the other. Amazon EventBridge is the serverless event bus that breaks that coupling. A producer calls PutEvents to publish an event; it neither knows nor cares who consumes it. Rules on the bus match an event pattern against the event’s fields — source, detail-type, and arbitrary detail JSON — and route every match to one or more targets (a Lambda, an SQS queue, a Step Functions state machine, another bus) that run in parallel.

{
  "Source": "checkout.service",
  "DetailType": "OrderPlaced",
  "Detail": {
    "orderId": "o-4821",
    "region": "eu-west-1",
    "amountCents": 7400,
    "needsTax": true
  }
}

A rule that fans this out to a tax-calculation Lambda and an analytics queue matches on the pattern below; anything not matching is silently ignored, so adding a consumer never touches the producer:

{
  "source": ["checkout.service"],
  "detail-type": ["OrderPlaced"],
  "detail": { "needsTax": [true] }
}

The default bus receives events from AWS services themselves (an EC2 state change, an S3 upload), which is how you build automation that reacts to your own infrastructure; custom buses carry your application’s domain events, isolating them from the AWS-service firehose. A schema registry can infer and version the shape of events so consumers code against a contract instead of a guess. The payoff is choreography: checkout emits OrderPlaced and walks away; tax, analytics, and email each subscribe independently. No service holds a list of who to call next, so you add and remove consumers without redeploying producers — exactly the coupling that turned a third-party SDK change into a cross-service incident.

Why this works

EventBridge, SNS, and SQS overlap enough to confuse, but they answer different questions. SNS is pub/sub fan-out: one message, many subscribers, no content filtering beyond coarse message attributes — pick it when you just need to broadcast fast and cheap. SQS is a queue: it buffers and decouples in time, letting a slow consumer drain work at its own pace with retries and a dead-letter queue — pick it when you need durability and backpressure between exactly two parties. EventBridge is a router: rich JSON pattern matching on event content, many sources to many targets, schema registry, and AWS-service events out of the box — pick it when routing logic lives in the content of the event and you want producers fully decoupled from a changing set of consumers. They compose: EventBridge routes an event to an SQS queue that buffers it for a slow worker.

Structured logs: the third pillar, made queryable

Tracing tells you which hop; logs still tell you what happened inside that hop — but only if they’re queryable. A senior emits structured logs: JSON objects with named fields, not human prose. A line like “user 4821 failed payment, code 51” is unsearchable; a record like {“level”:“error”,“traceId”:“1-5759e988”,“userId”:4821,“event”:“payment_failed”,“declineCode”:51} lets you pull every log for one trace ID and join it to the X-Ray timeline. Carrying the trace ID into your logs is what fuses pillars two and three. CloudWatch’s Embedded Metric Format (EMF) goes further: emit a specially shaped JSON log and CloudWatch extracts metrics from it automatically, so you get a metric and its supporting log from one write.

But fields cost money and the trap is cardinality. CloudWatch Logs bills per GB ingested (illustratively about $0.50/GB beyond a 5 GB free tier in US East, region-dependent), and EMF turns high-cardinality dimensions into custom metrics at roughly $0.30 per metric per month. Put userId or requestId as an EMF dimension and every distinct value becomes its own billed metric — a million users becomes a million metrics and a four-figure monthly line item from one careless field. Log fields freely; keep metric dimensions low-cardinality (status, route, region — not user or request id). Over-instrumenting is a failure mode exactly like over-sampling.

Question you’re askingPillar that answers itAWS surfaceCost lever to watch
Is a service unhealthy right now?MetricsCloudWatch metrics / alarmsCustom-metric dimension cardinality (~$0.30/metric/mo)
What exactly happened inside one box?Logs (structured)CloudWatch Logs / EMFGB ingested (~$0.50/GB beyond free tier)
Which hop in the chain is slow?TracesX-Ray service mapSampling rate (~$5/M traces recorded)
How do I stop hop A and hop B coupling?Event-driven designEventBridge bus + rulesPer-event PutEvents + per-target invocations
Pick the best fit

An OrderPlaced event must fan out to three independent consumers (tax calc, analytics, email), each owned by a different team that comes and goes; routing depends on fields inside the event (region, needsTax). Pick the decoupling primitive.

Quiz

A request chain spanning five services is intermittently slow, but every service's CPU, memory, and its own p99 latency metric look normal. Which observability tool is built to locate the slow hop?

Quiz

A team sets X-Ray sampling to trace 100% of requests on a path serving 5,000 requests/second and is shocked by the bill. What's the principle they violated?

Recall before you leave
  1. 01
    What does a distributed trace show that metrics and logs structurally cannot, and how does X-Ray model it?
  2. 02
    Why is observability a cost decision, and how do sampling and EventBridge fit in?
Recap

Observability stands on three pillars, and seniors keep them straight: metrics answer “is a service unhealthy?” (per-box numbers, CloudWatch alarms), structured logs answer “what exactly happened inside one box?” (queryable JSON with a trace ID carried in, EMF to mint metrics from logs), and distributed traces answer “which hop in a request chain is slow?” — the one logs and metrics structurally cannot, because they live inside a single node while the latency often lives in the edges between services. X-Ray models a request as a trace identified by one trace ID: each service emits a segment of its own work, each downstream call is a subsegment, the X-Amzn-Trace-Id header propagates the trace across process boundaries, and X-Ray draws a service map with latency- and error-annotated edges that points straight at the slow hop. Because tracing and high-cardinality logging cost money and add latency, observability is a budget decision: sample traces (the default first-request-per-second plus 5% trades completeness for cost on purpose; 100% on a busy path is a five-figure mistake) and bound log/metric cardinality (userId as an EMF dimension explodes into a million billed metrics). Finally, EventBridge keeps the hops you trace from coupling in the first place: producers PutEvents and walk away, rules match on source/detail-type/detail and fan out to targets in parallel — a content router you reach for over SNS (simple fan-out) and SQS (buffer/backpressure) when routing lives in the event’s content and consumers come and go. Now when you see a multi-service latency mystery where every service’s metrics look fine, you open the X-Ray service map first — and when you see a synchronous call that could fail or slow down an unrelated service, you ask whether EventBridge would decouple it.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 5 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.