When Observability Saved Us From a Blameless Post-Mortem

The system was slow.

But the dashboards looked healthy.

We had CPU utilization under 50%.

Memory was stable.

Error rates were below 1%.

Our monitoring alerts weren’t triggering.

And yet customers were complaining about slowness.

This was the contradiction we couldn’t explain.

Everything appeared green.

The experience was red.

The first instinct was to assume our monitoring was lying.

We added more alerts.

We lowered thresholds.

We expanded the dashboard views.

Nothing worked.

Then we realized something uncomfortable:

We weren’t seeing the problem because we weren’t measuring it correctly.

We were monitoring infrastructure.

We weren’t observing the business flow.

That distinction changed everything.

1. The Symptom We Couldn’t Explain

The incident started with customer complaints.

Support tickets mentioned payment delays.

Checkout pages hung for several seconds.

API responses that normally completed in milliseconds were taking seconds.

But when we looked at the system metrics:

Application CPU:      42%
Memory Usage:         58%
Database CPU:         65%
Error Rate:           0.3%
Request Success Rate: 99.7%

All within normal ranges.

This created a confusing situation:

  • Customers reported real problems
  • Dashboards showed no problems
  • Alerts remained silent
  • Infrastructure looked healthy

We began investigating with the assumption that something was hidden from our view.

The problem wasn’t that nothing was happening.

The problem was that we weren’t looking at the right thing.

2. The First Mistake: Monitoring Instead of Observability

We had confused monitoring with observability.

Monitoring tells you:

“Is something broken?”

Observability tells you:

“Why did it break?”

Our dashboards answered the first question.

They told us servers were up, services were responding, and error rates were low.

But they didn’t answer the second question.

We couldn’t trace a single customer transaction across our system.

We couldn’t see where requests spent their time.

We couldn’t correlate events across services.

This is what the original architecture looked like:

Customer Request
   │
   ▼
API Gateway ──► [Logs: OK]
   │
   ▼
Service A ──► [Logs: OK]
   │
   ▼
Service B ──► [Logs: OK]
   │
   ▼
Database ──► [Logs: OK]
   │
   ▼
Response

Each component logged its own success.

But there was no connection between them.

A request could fail somewhere in the middle and no single component would know.

We had visibility into each piece.

We had no visibility into the whole.

3. The Investigation That Changed Everything

We decided to trace one customer transaction end-to-end.

Not with synthetic data.

With a real request.

We followed it through every service.

The pattern we discovered was revealing:

T+0ms    Client sends request
T+5ms    API Gateway receives request
T+50ms   Service A processes request
T+55ms   Service A calls Service B
T+60ms   Service B receives request
T+450ms  Service B waits for Service C
T+500ms  Service C responds
T+510ms  Service B returns to Service A
T+520ms  Service A returns to Gateway
T+525ms  Gateway returns to Client

Total time: ~520ms

Normal response time: ~80ms

The latency spike happened between T+60ms and T+450ms.

Service B was waiting for Service C.

But Service C wasn’t timing out.

It wasn’t returning errors.

It was simply slow.

And none of our infrastructure dashboards captured this.

We had been looking at component health.

We weren’t looking at request flow.

4. The Correlation ID Breakdown

The deeper problem emerged when we tried to follow the transaction through logs.

Each service had its own logs.

Each log entry had a timestamp.

But there was no way to connect them.

Service A Log:
[10:23:45] Request received
[10:23:45] Calling Service B

Service B Log:
[10:23:45] Request received
[10:23:45] Calling Service C

Service C Log:
[10:23:45] Request received
[10:23:46] Processing...
[10:23:46] Response sent

No correlation.

No request ID.

No way to know which Service C call belonged to which Service A request.

This is why the investigation felt like searching in the dark.

We had data.

But it wasn’t connected.

The fundamental issue was that correlation IDs weren’t being propagated across service boundaries.

A client request could arrive with an ID.

But once it crossed into Service B, that ID was lost.

Service C saw a different ID, or no ID at all.

We were looking at disconnected islands of information.

5. The Three Approaches We Considered

Once we understood the observability gap, we had several options.

Option 1: Add More Alerts

This was the easiest option.

Lower latency thresholds.

Add more dashboards.

Create more notifications.

The problem?

We’d be adding noise to the same blind spots.

More alerts don’t give you visibility.

They just tell you about problems you already know exist.

Option 2: Upgrade the Monitoring Stack

The second option was to invest in enterprise observability tools.

APM solutions.

Distributed tracing platforms.

Log aggregation systems.

This could help.

But it wouldn’t fix the root problem.

Tools don’t create observability.

Instrumentation does.

We could have the best platform in the world and still be blind if the data wasn’t flowing through it correctly.

Option 3: Redesign Observability Into the Architecture

This was the direction we chose.

Instead of asking:

“What monitoring do we need?”

we asked:

“What data do we need to answer our questions?”

That changed the investigation completely.

We needed to answer questions like:

  • Where does a request spend its time?
  • Which service is the bottleneck?
  • What business operation failed?
  • How many transactions are affected?
  • Can we trace this back to a customer?

Those aren’t infrastructure questions.

They’re business questions.

And answering them required different instrumentation.

6. What We Actually Changed

The solution wasn’t buying new tools.

It was instrumenting the existing system differently.

We made three fundamental changes.

Change 1: Correlation IDs Propagated Everywhere

Every incoming request received a unique ID at the API gateway.

That ID was then carried through every service call.

Every database query.

Every message queue operation.

Every log entry.

The flow looked like this:

Request
   │
   ▼
┌─────────────────────────────────────┐
│ correlation-id: abc123-def456       │
│ request-timestamp: 2026-08-14T10:23 │
│ customer-id: 847291                 │
└─────────────────────────────────────┘
   │
   ▼
API Gateway ──[passes correlation-id]──► Service A
                                                │
                                                ▼
                                         Service B ──[passes correlation-id]──► Service C

Now every log entry, metric, and trace was connected.

We could follow a single transaction across the entire system.

Change 2: Business Metrics Alongside Infrastructure Metrics

We stopped measuring only CPU, memory, and latency.

We started measuring business-relevant signals:

Infrastructure MetricBusiness Metric
Request latencyPayment processing time
Error rateFailed transaction rate
CPU utilizationTransactions per second
Connection poolActive checkout sessions
Queue depthPending settlements

The infrastructure metrics told us if the system was running.

The business metrics told us if the system was working.

Change 3: Structured Logging With Context

Raw text logs became structured JSON with context:

{
  "timestamp": "2026-08-14T10:23:45Z",
  "correlation_id": "abc123-def456",
  "customer_id": "847291",
  "service": "payment-service",
  "operation": "process_payment",
  "duration_ms": 520,
  "status": "success",
  "upstream_service": "checkout-api",
  "downstream_services": ["ledger-db", "fraud-check"]
}

Every log entry now contained the context needed to understand it.

No more searching through timestamps to connect related events.

7. What Changed After the Fix

After implementing these changes, we tested the system with the same workload that had revealed the problem.

Here’s what we observed:

Before:

  • p95 latency: 520ms (unexplained spike)
  • Investigation time per incident: 4-6 hours
  • Mean time to resolution: 2-3 hours
  • Blameless post-mortems: Rare (because we couldn’t find root causes)
  • Correlation ID coverage: 0% (logs unconnected)

After:

  • p95 latency: 85ms (stable)
  • Investigation time per incident: 15-30 minutes
  • Mean time to resolution: 45-60 minutes
  • Blameless post-mortems: Regular (root causes now visible)
  • Correlation ID coverage: 100% (end-to-end traces)

But more importantly:

We could now answer questions that previously were impossible:

“How many customers were affected by the latency spike?”

Before: Unknown. After: 247 customers in 12 minutes.

“Which service caused the delay?”

Before: Guesswork. After: Service C (fraud-check) at T+450ms.

“Can we trace this specific failed transaction?”

Before: Impossible. After: Full trace available with correlation ID lookup.

8. The Lesson: Observability Is Architecture

This incident taught us a principle we still apply:

Observability is not an operational concern. It is an architectural concern.

You cannot bolt on observability after the fact.

You cannot add it to a system that wasn’t designed for it.

If you want to trace transactions, you must propagate IDs.

If you want to measure business outcomes, you must instrument them.

If you want to debug distributed systems, you must structure your logs.

These aren’t implementation details.

They are design decisions.

And they need to be made before you go to production.

9. When You Should Suspect an Observability Gap

There are several signals that indicate your observability isn’t adequate:

1. Dashboards look healthy but customers complain

This means you’re measuring the wrong things.

2. Investigations take hours or days

This means you lack the data to connect events.

3. Teams argue about who caused the failure

This means you lack shared visibility.

4. Post-mortems have “unknown root cause”

This means you lack traceability.

5. You can’t answer “how many customers were affected?”

This means you lack business context in your metrics.

6. Logs are disconnected across services

This means you lack correlation ID propagation.

If you recognize any of these, your observability isn’t keeping pace with your architecture.

10. The Questions I Ask Now

When evaluating observability in a system, I don’t ask:

“What tools do you use?”

I ask:

“Can you trace a customer request end-to-end?”

Then:

“Does every log entry have a correlation ID?”

Then:

“Can you measure business outcomes directly?”

And finally:

“If something breaks at 2AM, can you diagnose it in 15 minutes?”

Those questions tell me much more than whatever monitoring stack a team uses.

11. Final Thought

The most dangerous problems aren’t always the ones that crash your system.

Sometimes they’re the ones that make your system slower while everything looks green.

The problem wasn’t that our system was broken.

The problem was that we couldn’t see how it was broken.

We were monitoring components.

We weren’t observing the system.

That was the difference.

The lesson I took away was simple:

You can’t fix what you can’t see. And you can’t see what you haven’t designed for.

In financial systems, that’s especially critical.

Payment latency equals customer trust loss.

Transaction errors equal regulatory exposure.

And without proper observability, you’re flying blind into both.

So design your observability before you design your scaling.

Because when the system slows down at 2AM, you’ll wish you could see what’s actually happening.

Not what the dashboards say.

The truth.

Observability isn’t about seeing that something broke. It’s about understanding why.

Leave a Reply

Your email address will not be published. Required fields are marked *