Skip to main content

12 - Case Study - End-to-End Observability Implementation (Microservices)

Case Study: Implementing End-to-End Observability for Microservices. In this final article of the series, we analyze a real-world implementation example...

What do you think about this article?

At a Glance

Case Study: Implementing End-to-End Observability for Microservices. In this final article of the series, we analyze a real-world implementation example...

Case Study: Implementing End-to-End Observability for Microservices

In this final article of the series, we will analyze a real-world case of observability implementation in a microservices architecture. We will follow the journey of an e-commerce startup with 5 microservices adopting OpenTelemetry to solve chronic debugging problems, reduce Mean Time To Resolution (MTTR), and gain complete visibility into business flows.

We will document every phase of the implementation, from the initial situation (zero observability) to the complete production stack, with before and after metrics that quantify the impact of the observability investment.

What You Will Learn in This Article

  • How to plan an observability implementation step-by-step
  • The complete stack configuration for 5 microservices
  • Instrumentation patterns for a real e-commerce flow
  • Grafana dashboards for operational and business monitoring
  • Before/after metrics: MTTR, incident detection, SLO compliance
  • Lessons learned and adoption recommendations

The Context: ShopFlow E-Commerce Platform

ShopFlow is a microservices-based e-commerce platform with the following stack:

ShopFlow Architecture

Service Language Database Responsibility
API Gateway Node.js (Express) Redis (cache) Routing, authentication, rate limiting
Order Service Java (Spring Boot) PostgreSQL Order creation, state management
Inventory Service Python (FastAPI) PostgreSQL Stock management, reservations
Payment Service Java (Spring Boot) PostgreSQL Payments, refunds
Notification Service Python (FastAPI) MongoDB Email, SMS, push notifications

Services communicate via synchronous HTTP and Kafka for asynchronous events. Deployment is on Kubernetes (EKS) with approximately 500 orders/hour at peak.

Situation Before Observability

Before OpenTelemetry adoption, ShopFlow had limited system visibility:

  • Unstructured logs: each service logged in a different format, without trace_id. Finding logs for a request required manual grep across 5 services
  • Basic metrics: only infrastructure metrics (CPU, memory, disk) from CloudWatch, no application metrics
  • Zero tracing: impossible to follow a request across services. Debugging cross-service issues took days
  • Reactive alerts: alerts only on CPU > 80% and generic 5xx errors, without context on error type or business impact

Pre-Observability Metrics (Baseline)

Metric Value
MTTR (Mean Time To Resolve) 4.5 hours
MTTD (Mean Time To Detect) 45 minutes
P1 incidents/month 8
SLO compliance (99.5%) 94% of months
Cross-service debug time 2-4 hours
Incident cost/month (estimate) $12,000

Phase 1: Auto-Instrumentation and Collector (Week 1-2)

The first phase focuses on deploying the observability infrastructure and auto-instrumenting services, without modifying application code.


# Phase 1: Deploy OTel Collector as DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-collector-agent
  namespace: observability
spec:
  selector:
    matchLabels:
      app: otel-agent
  template:
    spec:
      containers:
        - name: collector
          image: otel/opentelemetry-collector-contrib:0.96.0
          args: ["--config=/etc/otel/config.yaml"]
          env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
          ports:
            - containerPort: 4317
              hostPort: 4317
---
# Auto-instrumentation for Java services
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: shopflow-instrumentation
  namespace: shopflow
spec:
  exporter:
    endpoint: http://otel-collector-agent.observability:4317
  propagators:
    - tracecontext
    - baggage
  sampler:
    type: parentbased_traceidratio
    argument: "1.0"  # 100% in initial phase
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
  python:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
  nodejs:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest
---
# Annotate deployments for auto-instrumentation
# Order Service (Java)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: shopflow
spec:
  template:
    metadata:
      annotations:
        instrumentation.opentelemetry.io/inject-java: "shopflow-instrumentation"
      

Phase 2: Manual Instrumentation and Log Correlation (Week 3-6)

In the second phase, the team adds manual instrumentation for critical business flows and configures log-trace correlation to link logs to distributed traces.


// Order Service: manual instrumentation of checkout flow
@Service
public class CheckoutService {

    private final Tracer tracer;
    private final LongCounter ordersCreated;
    private final DoubleHistogram orderValue;

    public CheckoutService(OpenTelemetry otel) {
        this.tracer = otel.getTracer("order-service");
        Meter meter = otel.getMeter("order-service");
        this.ordersCreated = meter.counterBuilder("shopflow.orders.created")
            .setDescription("Orders created").build();
        this.orderValue = meter.histogramBuilder("shopflow.orders.value")
            .setDescription("Order value in EUR").setUnit("EUR").build();
    }

    public Order processCheckout(CheckoutRequest req) {
        Span span = tracer.spanBuilder("checkout.process")
            .setAttribute("customer.id", req.getCustomerId())
            .setAttribute("customer.tier", req.getCustomerTier())
            .setAttribute("cart.items_count", req.getItems().size())
            .setAttribute("cart.total", req.getTotal())
            .startSpan();

        try (Scope scope = span.makeCurrent()) {
            // Validation
            Span valSpan = tracer.spanBuilder("checkout.validate").startSpan();
            try (Scope s = valSpan.makeCurrent()) {
                validateCheckout(req);
                valSpan.setStatus(StatusCode.OK);
            } finally { valSpan.end(); }

            // Reserve inventory
            Span invSpan = tracer.spanBuilder("checkout.reserve-inventory")
                .setAttribute("inventory.items", req.getItems().size())
                .startSpan();
            try (Scope s = invSpan.makeCurrent()) {
                reserveInventory(req.getItems());
            } finally { invSpan.end(); }

            // Payment
            Span paySpan = tracer.spanBuilder("checkout.payment")
                .setAttribute("payment.method", req.getPaymentMethod())
                .setAttribute("payment.amount", req.getTotal())
                .startSpan();
            try (Scope s = paySpan.makeCurrent()) {
                processPayment(req);
            } finally { paySpan.end(); }

            // Create order
            Order order = createOrder(req);
            span.setAttribute("order.id", order.getId());
            span.setAttribute("order.status", "created");

            // Business metrics
            ordersCreated.add(1, Attributes.of(
                AttributeKey.stringKey("customer.tier"), req.getCustomerTier(),
                AttributeKey.stringKey("payment.method"), req.getPaymentMethod()
            ));
            orderValue.record(req.getTotal(), Attributes.of(
                AttributeKey.stringKey("customer.tier"), req.getCustomerTier()
            ));

            span.setStatus(StatusCode.OK);
            return order;
        } catch (Exception e) {
            span.recordException(e);
            span.setStatus(StatusCode.ERROR, e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
}
      

Phase 3: Dashboards, Alerting, and SLOs (Month 2-3)

In the third phase, the team creates Grafana dashboards for operational and business monitoring, configures SLO-based alerting, and optimizes sampling to reduce costs.


# SLO-based alerts for ShopFlow
groups:
  - name: shopflow-slo-alerts
    rules:
      # SLO: 99.5% checkout requests successful
      - alert: CheckoutSLOBreach
        expr: |
          1 - (
            sum(rate(http_server_request_duration_seconds_count{
              service="order-service",
              http_route="/api/checkout",
              http_status_code=~"2.."
            }[1h]))
            /
            sum(rate(http_server_request_duration_seconds_count{
              service="order-service",
              http_route="/api/checkout"
            }[1h]))
          ) > 0.005
        for: 5m
        labels:
          severity: critical
          slo: checkout-success-rate
        annotations:
          summary: "Checkout success rate below 99.5% SLO"

      # SLO: P99 checkout latency under 3 seconds
      - alert: CheckoutLatencySLO
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_server_request_duration_seconds_bucket{
              service="order-service",
              http_route="/api/checkout"
            }[5m])) by (le)
          ) > 3
        for: 5m
        labels:
          severity: warning
          slo: checkout-latency
        annotations:
          summary: "Checkout P99 latency above 3s SLO"

      # SLO: payment success above 98%
      - alert: PaymentSuccessRateSLO
        expr: |
          sum(rate(shopflow_payments_total{status="success"}[1h]))
          /
          sum(rate(shopflow_payments_total[1h])) < 0.98
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Payment success rate below 98%"
      

Results: Before and After Metrics

After 3 months of full implementation, ShopFlow measured the following improvements:

Observability Impact on ShopFlow

Metric Before After Improvement
MTTR 4.5 hours 1.2 hours -73%
MTTD 45 minutes 3 minutes -93%
P1 incidents/month 8 3 -62%
SLO compliance 94% 99.2% +5.2pp
Cross-service debug 2-4 hours 10-30 minutes -87%
Incident cost/month $12,000 $3,200 -73%

Lessons Learned

The observability implementation at ShopFlow produced several useful lessons for any organization undertaking the same journey:

Key Recommendations

  • Start with auto-instrumentation: 70% of the value arrives in the first month with auto-instrumentation and Collector, without modifying code
  • Invest in correlation: log-trace correlation is the single improvement with the highest ROI. It reduces debug time by 90%
  • Define SLOs before dashboards: SLOs guide the choice of which metrics to collect and which alerts to configure
  • Don't instrument everything immediately: start with the 3-5 most critical business flows, expand gradually
  • Monitor the Collector: the Collector is a critical component. If it fails, you lose all visibility
  • Tail sample errors: after the initial phase (100% sampling), implement tail sampling while keeping 100% of errors
  • Train the team: observability has value only if the team knows how to use the tools. Invest in Grafana, PromQL, and trace reading training

Implementation Cost

It is important to document the observability investment cost to calculate ROI. For ShopFlow, the open source stack had a cost primarily in engineering time and infrastructure resources:

Total Implementation Cost

Item Estimated Cost
Engineering time (setup + instrumentation) ~120 hours (3 engineers x 2 weeks full + part-time)
Collector infrastructure (DaemonSet + Gateway) ~$200/month (CPU + memory on EKS)
Backend storage (Jaeger, Prometheus, Loki) ~$350/month (EBS volumes + compute)
Grafana Cloud (alternative to self-hosted) $0 (self-hosted) or ~$500/month (cloud)
Monthly total ~$550/month (self-hosted)
Incident savings ~$8,800/month
ROI 16x (savings / cost)

Implementation Timeline

The complete journey from idea to production took approximately 3 months, with incremental value at each phase:

Summary Timeline

Week 1: Deploy Collector, backends (Jaeger, Prometheus, Grafana), auto-instrumentation on all 5 services. First value: distributed traces visible in Jaeger.

Week 2: Configure Collector pipeline (filtering, batching). Grafana dashboards with RED metrics for each service.

Week 3-4: Manual instrumentation of checkout and payment flows. Log-trace correlation with Loki. First cross-service debug in 15 minutes (previously took 3 hours).

Week 5-6: Custom business metrics (orders/hour, revenue, conversion rate). Dashboard for the product team.

Month 2: SLO definition, SLO-based alerting, tail sampling. Reduced trace volume by 80% while keeping 100% of errors.

Month 3: Optimization, team training, runbook documentation. Stabilization and results measurement.

Series Conclusions

This 12-article series has covered the entire spectrum of modern observability with OpenTelemetry, from theoretical foundations (Three Pillars, monitoring vs observability) to advanced implementations (eBPF, AI observability, tail sampling) to this practical case study with real metrics.

The key messages of the series are:

  1. Observability is a system property, not a product to purchase. It is built through careful instrumentation and signal correlation.
  2. OpenTelemetry is the standard: instrument once, export anywhere. The freedom to change backends without modifying code is a strategic advantage.
  3. Start simple, evolve gradually: auto-instrumentation in month 1, manual instrumentation in month 2, optimization in month 3.
  4. Correlation is the value multiplier: linking traces, logs, and metrics reduces debug time by 90%.
  5. Observability has measurable ROI: reducing MTTR and incidents translates directly into cost savings and better user experience.

Observability is not a cost, it is an investment that pays for itself quickly in distributed systems. With OpenTelemetry, the standard is mature, the tools are available, and the adoption path is well documented. The best time to start is now.

Frequently asked questions

What is the main goal of implementing observability in a microservices architecture?

The main goal is to solve chronic debugging problems, reduce Mean Time To Resolution (MTTR), and gain complete visibility into business flows.

How many services are part of the ShopFlow e-commerce platform?

There are 5 microservices that make up the ShopFlow e-commerce platform.

What is the primary cost associated with implementing observability in a system like ShopFlow?

The primary cost is engineering time and infrastructure resources, as the open source stack used for observability does not incur significant licensing fees.

How long did it take to implement observability from start to finish at ShopFlow?

It took approximately 3 months to complete the implementation.

Sources and further reading

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects