Back to Observability: Logging, Tracing, Metrics
Q3Question 3 of 9

Why all services can be green while the product is broken: business metrics and synthetic journeys

Key Concepts
  • Per-service signals (readiness probes, CPU, 5xx rate) only say each part is individually alive, not that the composed flow works.
  • Failures in the seams: a flipped feature flag, bad data, or a silent logical error causes no crash but breaks the product.
  • Business-outcome metrics (payment-success rate, order-completion rate) measure the result the customer cares about.
  • Alert on the ratio of completions to initiations and on sudden drops to catch composition-level failures.
  • Synthetic journeys run a real end-to-end flow continuously against production, catching seam failures even during quiet hours.
  • Per-service probes localize the fault after a business metric says there is one: they are complementary, not a replacement.
  • Tag synthetic traffic so it never pollutes real business metrics.

Definition

Green services mean the parts are alive; a green business metric means the product actually works: and only the second one tells you the customer is okay.
  • Per-service signals – liveness/readiness probes, CPU, memory, HTTP 5xx rate. These tell you each individual service is running.
  • Seam failures – bad data, a flipped feature flag, or a silent logical error causes no crash in any service but breaks the composed business flow.
  • Business-outcome metrics – payment-success rate, order-completion rate. Measured at the outcome boundary, not inside one service.
  • Synthetic journeys – an automated client that continuously executes a real end-to-end flow against production, asserting the business outcome.
  • Complementary layers – business metric says 'product is broken,' synthetic journey says 'where in the flow,' per-service dashboards and traces say 'why.'

Imagine National Bank of India's branch where every department reports 'we are open and staffed.' The teller counter is green, the vault is green, the signature-verification desk is green. Yet Rajesh cannot complete a transfer, because the form passed between departments has the wrong field filled in. Every department is 'healthy,' but the customer walked out without his transfer. A synthetic journey is like sending a secret shopper through the whole process every minute and raising an alarm the moment the end-to-end transfer fails.

Understanding the Concepts

Why can all services be green while the product is broken?

The core issue is a mismatch of scope. A readiness probe answers: 'Can this one Pod accept traffic right now?' A 5xx rate answers: 'Is this one service throwing server errors?' Both can be perfectly healthy while the business outcome fails.

TEXT
Scenario: Feature flag accidentally turned off.

Payment Service:
  Health check: 200 OK (green)
  CPU: 15% (normal)
  5xx rate: 0% (perfect)
  What it does: accepts every transfer, returns HTTP 200,
    writes status as "INITIATED" but the settlement flag is OFF.
    No payment ever moves to "COMPLETED".

Result: every dashboard is green, but no customer's money moves.

What are the common ways this happens?

The failures that slip through live in the seams between services, not inside any single service.

JAVA
package com.bank.seam;

import java.math.BigDecimal;
import java.util.logging.Logger;

public class SeamFailureExamples {

    private static final Logger LOG = Logger.getLogger(SeamFailureExamples.class.getName());

    // Scenario 1: Feature flag flipped off.
    // Payment Service returns 200 but marks as "INITIATED" forever.
    public void scenarioFeatureFlag(String paymentId) {
        boolean settlementEnabled = false; // accidentally turned off
        if (settlementEnabled) {
            // settle(paymentId);
        }
        LOG.info("paymentId=" + paymentId + " status=INITIATED"); // no error logged
    }

    // Scenario 2: Wrong exchange rate from pricing service.
    // Transfer "succeeds" but with incorrect amounts.
    public void scenarioWrongData(String paymentId, BigDecimal rate) {
        BigDecimal amount = new BigDecimal("50000").multiply(rate); // rate is wrong
        LOG.info("paymentId=" + paymentId + " converted amount=" + amount); // looks fine
    }

    // Scenario 3: Schema change causes silent field drop.
    // Notification service consumes events but drops them because
    // a required field is now null after a schema change.
    public void scenarioSchemaChange(String paymentId, String email) {
        if (email == null) {
            LOG.info("paymentId=" + paymentId + " skipping notification (no email)");
            return; // silently does nothing, no error
        }
    }
}
TEXT
In all three scenarios:
- Health checks: 200 OK (green)
- CPU/memory: normal
- Error rate: 0%
- Per-service dashboard: all green
- Customer experience: broken

What are business-outcome metrics?

Instead of only counting HTTP statuses, emit a metric for the thing the customer actually wanted.

JAVA
package com.bank.metrics;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

@Component
public class TransferOutcomeMetrics {

    private final Counter initiated;
    private final Counter completed;
    private final Counter failed;

    public TransferOutcomeMetrics(MeterRegistry registry) {
        this.initiated = registry.counter("transfers.initiated");
        this.completed = registry.counter("transfers.completed");
        this.failed = registry.counter("transfers.failed");
    }

    public void recordInitiated() { initiated.increment(); }
    public void recordCompleted() { completed.increment(); }
    public void recordFailed()    { failed.increment(); }
}
TEXT
Prometheus metrics:
transfers_initiated_total  1523
transfers_completed_total  0     <- ALERT: completions dropped to zero
transfers_failed_total     0

Payment-success rate = completed / initiated = 0 / 1523 = 0%
Alert fires: "Transfer completion rate below 95% SLO"
All per-service dashboards are still green.

How do you define SLIs and SLOs for business metrics?

A Service Level Indicator (SLI) is the metric you measure. A Service Level Objective (SLO) is the target you alert on.

TEXT
SLI: Transfer completion rate = transfers.completed / transfers.initiated
SLO: Transfer completion rate >= 99.5% over 5-minute window

Prometheus alerting rule:
alert: TransferCompletionRateLow
expr: rate(transfers_completed_total[5m]) / rate(transfers_initiated_total[5m]) < 0.995
for: 2m
labels:
  severity: critical
annotations:
  summary: "Transfer completion rate below 99.5% SLO"

What are synthetic journeys?

A synthetic journey is an automated client that continuously performs a real end-to-end flow against production.

JAVA
package com.bank.synthetic;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.logging.Logger;

@Component
public class SyntheticTransferProbe {

    private static final Logger LOG = Logger.getLogger(SyntheticTransferProbe.class.getName());
    private final RestTemplate restTemplate;

    public SyntheticTransferProbe(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Scheduled(fixedRate = 60000) // every 60 seconds
    public void runSyntheticTransfer() {
        try {
            // Use dedicated synthetic accounts that never affect real customers
            var request = new TransferRequest(
                "SYNTH-FROM-001", "SYNTH-TO-001", new BigDecimal("1.00"));

            var response = restTemplate.postForEntity(
                "http://api-gateway/api/v1/transfers", request, TransferResponse.class);

            // Assert the transfer reaches COMPLETED within 30 seconds
            String paymentId = response.getBody().getPaymentId();
            boolean completed = pollForCompletion(paymentId, 30_000);

            if (completed) {
                LOG.info("Synthetic transfer PASSED: " + paymentId);
            } else {
                LOG.severe("Synthetic transfer FAILED: " + paymentId
                    + " did not complete within 30s");
                // Fire alert
            }
        } catch (Exception e) {
            LOG.severe("Synthetic transfer ERROR: " + e.getMessage());
        }
    }

    private boolean pollForCompletion(String paymentId, long timeoutMs) {
        // Poll payment status until COMPLETED or timeout
        return true; // simplified
    }
}
TEXT
Expected behavior:
Every 60 seconds: create test transfer from SYNTH-FROM-001 to SYNTH-TO-001
Assert: transfer reaches COMPLETED within 30 seconds

Settlement flag OFF -> synthetic transfer stays INITIATED -> alert fires
All per-service dashboards still green, but synthetic probe caught it.

How do the three layers complement each other?

Business metrics, synthetic journeys, and per-service probes form three complementary layers.

| Layer | Question Answered | Signal Speed |
|---|---|---|
| Business-outcome metrics | Is the customer okay? | Within minutes (needs traffic) |
| Synthetic journeys | Where in the flow does it break? | Within 1 minute (runs continuously) |
| Per-service dashboards + traces | Why is it broken? | Immediate (always available) |

What are common mistakes?

The most common mistake is assuming green health checks mean the product works.

JAVA
package com.bank.antipattern;

// WRONG: Only per-service health checks. Misses composition-level failures.
public class WrongMonitoring {
    // readinessProbe: /actuator/health -> 200 OK
    // Conclusion: "Everything is fine."
    // Reality: No transfer has completed in 2 hours.
}

// CORRECT: Business metrics + synthetic journeys + per-service probes.
public class CorrectMonitoring {
    // 1. transfers.completed / transfers.initiated -> alert if < 99.5%
    // 2. Synthetic probe every 60s -> alert if transfer does not complete
    // 3. Per-service probes -> localize the root cause
}

What should a senior engineer know?

Tag synthetic traffic so it never pollutes real business metrics. Use dedicated synthetic accounts. Treat business metrics as SLIs with SLOs. The business metric says the product is broken, the synthetic journey says where, and the per-service dashboards and traces say why.

Visual Overview

DIAGRAM
  Three layers of observability:

  Layer 1: Business Outcome Metrics
  ┌─────────────────────────────────────────┐
  │ transfers.initiated: 1523               │
  │ transfers.completed: 0    <- ALERT      │
  │ "Is the customer okay?"  -> NO          │
  └─────────────────────────────────────────┘

  Layer 2: Synthetic Journeys
  ┌─────────────────────────────────────────┐
  │ Every 60s: create test transfer         │
  │ Assert: reaches COMPLETED in 30s        │
  │ Result: FAILED at step 3 (settlement)   │
  │ "Where in the flow?" -> Settlement step │
  └─────────────────────────────────────────┘

  Layer 3: Per-Service Dashboards + Traces
  ┌─────────────────────────────────────────┐
  │ Payment Service: 200 OK, 0% errors      │
  │ Settlement Service: 200 OK, 0% errors   │
  │ Trace: settlement flag = OFF            │
  │ "Why?" -> Feature flag accidentally off │
  └─────────────────────────────────────────┘

Key Takeaway

Every service can be green while the product is broken because per-service signals and business outcomes measure completely different things. A readiness probe only answers whether one Pod can accept traffic. A low 5xx rate only means the service is not throwing server errors. None of these say whether the composed end-to-end flow that Rajesh cares about actually succeeds. The failures that slip through live in the seams: a flipped feature flag, bad data passed downstream, or a silent logical error.

The fix is to measure the outcome, not just the parts. Business-outcome metrics like transfer-completion rate alert when the ratio drops, even though every per-service dashboard is green. Synthetic journeys run a real end-to-end flow every minute, catching seam failures even during quiet hours when real traffic is too sparse for statistical significance.

At National Bank of India, Pankaj deployed a change with a settlement flag accidentally turned off. All weekend every dashboard was green, but no transfer completed. After adding a payment-completion-rate SLO and a synthetic journey, the next composition-level fault was caught within a minute.

Remember one sentence: green services only prove the parts are alive, so always add business-outcome metrics and synthetic journeys that measure what the customer actually experiences.

---

Share
Use ← → arrow keys to navigate