TechVigil Logo

Navigating Sev-1 Outages at Scale: Architecture Patterns and Anti-Patterns

software-architecture
Sandeep Kumar
Sandeep KumarFounder & Software Architect

Software Architecture, DevOps & System Design

About Author →

In this article

  • Exploring the complexities of managing major incidents across distributed systems and why it matters as your product scales.
  • Understanding the essential components of effective observability using tools like Datadog, Splunk, and OpenTelemetry.
  • Uncovering the common anti-patterns in alerting that lead to alert fatigue and how to implement smart circuit breakers.
  • Managing communication effectively during a major crisis with dedicated incident commanders and cross-functional teams.
  • Learning from real-world missteps, including how a CMS outage broke application capabilities and the importance of isolated control planes.

The Bottom Line: Handling Sev-1 outages effectively requires more than just good engineering; it demands a cultural shift towards proactive observability and structured incident management. By standardizing logging, implementing smart circuit breakers, and isolating your operational control plane, you can drastically reduce the blast radius and mean time to recovery (MTTR) during major incidents.

Managing applications becomes challenging only when you hit a certain scale. If you're operating a single monolithic app or a small product with one tightly-knit team, there really isn't much complexity to worry about. But when you move to a distributed microservices architecture handling massive request volumes across dozens of teams, things break. Outages are simply inevitable.

What is a Sev-1 outage? A Sev-1 (Severity 1) outage is a critical incident where a core, customer-facing business function is completely down, requiring immediate all-hands intervention.

Over my career, I've navigated my fair share of these high-stakes incidents. Some were standard infrastructure hiccups, while others were unique, cascading failures that taught me hard lessons. In this post, I want to dive deep into my personal experiences, the standard frameworks I rely on, and the architecture patterns—and anti-patterns—that dictate how effectively an organization mitigates these major outages.

1. The Foundation of Observability

In a microservices architecture, your services are constantly spitting out a massive volume of logs. These are typically collected by platforms like Splunk, Elastic, or Datadog, while your UI applications might use platforms like Sentry or Datadog's RUM. I have personally used only these three backend loggers (Splunk, Elastic, Datadog), and each has its pros and cons. You need to find out what suits your team best.

Here is a quick breakdown based on my own experience in the trenches:

PlatformMy Personal TakeBest Used For
DatadogThe Apple ecosystem of monitoring. Everything "just works" beautifully together, but the pricing can spiral out of control if you don't monitor your host hours and custom metrics.Fast-moving, cloud-native teams wanting all-in-one APM, logs, and infrastructure without maintaining the tool itself.
SplunkAn absolute powerhouse for querying. If you take the time to learn SPL (Search Processing Language), you can extract anything. The downside? The UI feels dated and licensing by ingest volume hurts.Large enterprises with complex, unstructured logs and dedicated ops teams who can master SPL.
Elastic (ELK)Highly customizable with fantastic Kibana dashboards. However, it plays not-so-well with unindexed log attributes and fields, which can make querying unstructured data frustrating.Teams with strong DevOps chops who need deep customization or want to avoid high SaaS vendor premiums.

But no matter what tool you choose, the first rule I always enforce is ensuring logs have the correct severity level (Info, Warning, Error) and rich contextual information. A log without a stack trace or key identifiers-like an API resource ID-is practically useless during a firefight. It's extremely important to do logging right to avoid duplicate logs, which in turn cause duplicate, noisy alerts.

For example, if you're building a microservice in Go, you can use the standard log/slog package to easily bind this kind of rich context (like your trace_id) to your logger, making it trivial to distinguish between actionable errors and informational warnings:

package main
 
import (
	"context"
	"errors"
	"log/slog"
	"os"
)
 
func init() {
	// Set up a structured JSON logger (standard for Datadog/Splunk)
	logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
	slog.SetDefault(logger)
}
 
func processOrder(ctx context.Context, orderID string) error {
	// Extract trace-id from context (typically injected by your middleware)
	traceID, _ := ctx.Value("trace-id").(string)
	
	// Create a context-aware logger instance with our key identifiers
	log := slog.With(
		slog.String("trace_id", traceID),
		slog.String("order_id", orderID),
	)
 
	// Simulating a non-critical issue (Warning)
	// We want this in our daily reports, but it shouldn't wake anyone up at 3 AM.
	log.Warn("payment retried due to momentary network timeout", 
		slog.Int("attempt", 2))
 
	// Simulating a critical failure (Error)
	// This represents a hard failure that might contribute to an alerting threshold.
	err := errors.New("connection refused by payment gateway")
	if err != nil {
		log.Error("failed to process customer payment", 
			slog.String("error", err.Error()),
			slog.String("component", "payment_gateway"))
		return err
	}
 
	log.Info("order processed successfully")
	return nil
}

Along with your core application logs, you also need visibility into your sidecars and Istio containers. You need to see the full journey of a request. I always recommend using OpenTelemetry as the standard framework to generate a unified trace-id and span-id. This allows you to easily correlate logs from the exact same source across the entire request chain.

(By the way, if you are using Datadog, keeping your cloud costs in check is crucial-check out my post on taming Datadog infra host hours to optimize your usage. Also, a quick tip: debug logs should always be disabled in the production environment to keep your cloud bill lower by trimming non-essential noise!)

Beyond just debugging, these logging platforms are critical for continuous monitoring. We use dashboards to track API performance metrics like TP90/TP95/TP99, downstream dependency performance, call volume, and latency. Identifying the slowest component in a chain before it breaks is one of the greatest resources for maintaining operational excellence.

2. Smart Alerting and Circuit Breakers

Most logging platforms provide anomaly detection out-of-the-box, but custom rules that run at predefined intervals are where you get real control.

When an alert fires, the on-call engineer gets paged for initial triaging. Usually, alerts should be threshold-based. For example, if 5xx response codes cross a 1% threshold, that's something you want to know about immediately. However, 4xx errors might not be of a higher concern unless they spike to 20% or 30%, which could detect a completely different kind of issue. Alternatively, a single, high-severity error log might be enough to trigger an alert based on your specific needs.

Anti-Pattern Alert: I always warn teams to avoid creating alerts based on specific log messages, like failed to call service XYZ. This is a classic anti-pattern! Log messages are prone to change. A well-meaning developer might update the text to be more informative, entirely forgetting it's tightly coupled to an alert rule. Instead, alerts should be based on severity levels (e.g., "Error" always alerts, while "Warning" is grouped into a daily/weekly report) or HTTP response codes.

When alert triaging begins, it quickly reveals if we're dealing with an underlying data issue for a specific entity or a larger infrastructure degradation. If it's a one-off data issue or an unhandled scenario, the improvement is planned as per the competing priorities of the specific application owner team. But if it signals an infrastructure issue, the game changes entirely.

This is where cascading failures happen. When the lowest downstream service fails, it can propagate 503 errors all the way up the microservice chain. Without a circuit breaker, every service in the chain fires an alert. This creates immense noise and alert fatigue, making it incredibly difficult to find the real culprit.

Implementing a circuit breaker pattern is essential to avoid these redundant error messages that cause duplicate alerts for the exact same root workflow.

3. Limiting the Blast Radius: Bulkheads and Load Shedding

What is the difference between a circuit breaker and load shedding? Simply put: circuit breakers protect your downstream dependencies from being overwhelmed by you, while load shedding protects your service from being crushed by upstream traffic.

While circuit breakers are fantastic for stopping cascading failures, they are just one piece of the puzzle. Over the years, I've seen organizations get crushed because they didn't implement Bulkheads alongside their load shedding strategies.

Just like a ship has compartmentalized bulkheads to prevent the whole vessel from sinking if one section floods, your microservices need to isolate resources. Let's say you have a single, shared database connection pool or a global thread pool. If a non-critical microservice (like a product recommendation engine) suddenly hangs or starts consuming resources aggressively, it can starve the pool. Suddenly, your critical path-like the actual checkout service-can't get a connection.

By implementing the bulkhead pattern, you dedicate specific resource pools to specific services or endpoints. If the recommendation engine floods its compartment, the checkout compartment stays completely dry.

Similarly, while circuit breakers protect your downstream dependencies from being overwhelmed by you, Load Shedding protects your service from being crushed by upstream traffic. When your CPU is pinned at 99% and response times are skyrocketing, your service needs the ability to survive. Load shedding (or intelligent rate limiting) allows your service to intentionally drop non-essential incoming requests-returning a fast 429 Too Many Requests or 503 Service Unavailable-to ensure that the core, critical requests can still be processed. It's a brutal but necessary survival tactic.

4. Incident Command and Communication

Small organizations can easily use the email-based alerts provided by their logging platform to save costs if they don't need an on-call roster or escalation policies. But as you scale, that alert hand-off needs to happen in a dedicated platform like OpsGenie or PagerDuty. A dedicated platform provides easy collaboration, an incident timeline to support your Correction of Error (COE), an application catalog, and shift-rosters based on app owners and groups.

When OpsGenie wakes up the on-call engineer and they determine the blast radius is large and crosses team boundaries, a major incident is declared.

At this point, an Incident Commander (IC) takes the lead. More people across different teams are pulled onto Zoom or Teams calls to understand the blast radius. From here, teams often regroup in breakout rooms to carry out the technical investigation based on the nature of the incident. The IC stays in the main room and syncs back with the breakout rooms-typically every 15 minutes-with the help of key leads.

This separation of concerns is vital. While the diagnosis and fix are carried out by the engineering groups, the IC takes care of leadership communication and activates the public-facing banners and status pages. Crucially, this setup shields the engineering teams from getting distracted by executive questions or the intense pressure of leadership so they can focus entirely on solving the problem.

5. The Operational Control Plane

The incident commander decides the operational state of the applications and the impacted capabilities, reflecting this state publicly. In a modern 5-step operational control flow, this explicit declaration of the application's state happens right alongside the root cause investigation, treating machine reaction with the same rigor as human alerting.

Many companies use a dedicated operational state control plane. This allows consumer-facing applications to handle failing APIs gracefully. They can either hide the impacted capabilities entirely or show an appropriate banner within that specific capability screen to protect the customer experience.

Failing to do this causes a huge loss in brand trust, poor customer ratings, and eventually, churn. While agent tools or internal applications don't always require handling outages at a granular capability level (a global banner indicating "we know things are broken" is often sufficient), for end-consumer apps, this graceful degradation is absolutely crucial.

My Personal Takeaways / Lessons Learned

Outages are inevitable. The mark of a growing organization is how it learns from each incident: how best to avoid it in the future, how to reduce the impact, and how to make early detection easier. Mean Time to Acknowledge (MTTA) and Mean Time to Resolve (MTTR) both matter equally.

I've faced some pretty funny outages in my career that taught me unforgettable lessons. At one company, we used Zesty (a headless CMS) to store dynamic application text and promotional offers. The team also decided to use that same headless CMS to store our outage banner messages. When the CMS itself faced an outage, our promotion capabilities broke, and we were completely helpless to show the outage banner because it was part of the downed CMS!

Why shouldn't you use feature flags or standard CMS tools for outages? Because they couple your incident response to the very infrastructure that might be failing.

Here are my core best practices for surviving Sev-1 outages:

  • Isolate your control plane: Your operational state control plane must be completely isolated from the application delivery mechanism itself to avoid shared fate.
  • Actively restrict state changes: If your dependencies are under maintenance, your degradation strategies must enforce read-only modes, not just show a banner. I've seen unhandled state changes cause massive transactional inconsistencies.
  • Hold structured postmortems: Once the fire is out, schedule a Correction of Error (COE) review. Follow the incident timeline to identify the root cause and conduct an honest internal vs. vendor analysis.
Sandeep Kumar

Sandeep Kumar

Founder & Software Architect | System Design & DevOps

About Sandeep →

Electronics engineer and tech enthusiast specializing in software architecture, system design, and building scalable tech solutions. Passionate about sharing real-world engineering experiences, practical lessons, and tech insights.