Skip to content
Outlier.labs
Engineering Execution··8 min read

From Fragile to Fault-Tolerant: How High-Growth Teams Rearchitect Without Stopping Delivery

Rearchitecting a production system while continuing to ship features is one of the most demanding challenges in software engineering. Here is how to approach it incrementally.

OL

Outlier Labs

Engineering Team

Cover image for From Fragile to Fault-Tolerant: How High-Growth Teams Rearchitect Without Stopping Delivery
TOPOLOGYDEGRADED
edge
api
billing
db
breaker · auto-recover 12s
01

The pressure to rewrite

Rearchitecting a production system while continuing to ship features is one of the most technically and organizationally demanding challenges in software engineering. It's also one of the most common situations facing engineering teams at growth-stage companies: the system was built to work, not to scale, and the business has grown faster than the architecture.

The instinct is often to propose a full rewrite—a clean break that addresses all the design debt at once. In practice, rewrites of non-trivial systems have a poor track record. They take longer than estimated, they replicate the business logic errors of the original system, and they create a long period of parallel maintenance that drains engineering capacity. The more defensible approach is incremental rearchitecture: improving the system's fault tolerance, modularity, and scalability while keeping it in production.

02

Diagnosing the Fragility First

Not all fragility is architectural. Before undertaking rearchitecture, it's worth being precise about where the system actually fails. A system that struggles with database connection pool exhaustion under load has a different problem than a system with deeply coupled services that can't be independently deployed. Both might present as 'fragile,' but the remediation strategies are different.

A structured failure mode analysis maps the system's known failure points: which components fail first under load, which failures cascade into broader outages, which parts of the codebase require the most careful deployment coordination, and where the most significant operational incidents have occurred. This analysis converts 'the system is fragile' into a specific, prioritized list of problems—which is the only basis for an intelligent remediation plan.

Dependency mapping is an essential part of this analysis. Understanding which services depend on which other services, and how they communicate—whether via synchronous HTTP calls, asynchronous queue consumption, or direct database access—reveals where failures can cascade and where changes are most likely to have unintended effects. This map is also the input to the decomposition strategy.

03

The Strangler Fig Pattern and Incremental Decomposition

The strangler fig pattern, originally described by Martin Fowler, is the conceptual framework for most successful incremental rearchitecture efforts. The idea is to build new functionality alongside the existing system rather than replacing the existing system directly. Over time, new capabilities replace old ones, and the legacy system is progressively strangled—replaced piece by piece—rather than all at once.

In practice, this often means routing specific traffic types or user segments to new service implementations while the main system continues to handle the rest. Feature flags control which code path is active. The new implementation is validated against production traffic before the old one is retired. No single deployment is a high-stakes cutover.

Database decomposition is often the most difficult aspect of this work. Tightly coupled systems frequently share a single database across what should logically be separate domains. Decomposing this requires a careful sequence: identify logical domain boundaries, introduce abstraction layers that route reads and writes, gradually migrate data ownership to the new boundaries, and eventually enforce those boundaries in the data layer. This work is slow and unglamorous, but it's what enables true service independence.

04

Building Fault Tolerance Without a Rewrite

Fault tolerance improvements don't require a new architecture—they require adding resilience patterns to the existing one. Several can be applied incrementally:

Circuit breakers prevent cascading failures by stopping requests to a failing dependency before those failures propagate. Libraries like Resilience4j (JVM), Polly (.NET), and pybreaker (Python) provide circuit breaker implementations that can be added around existing external calls without restructuring the surrounding code.

Retry policies with exponential backoff handle transient failures—network hiccups, brief database unavailability—that would otherwise surface as errors to the end user. Adding retry logic to HTTP client configurations and database connection pools is low-risk and provides meaningful reliability improvement.

Timeouts on all external calls prevent slow dependencies from occupying thread pool capacity and degrading the whole system. Systems that don't enforce timeouts are vulnerable to scenarios where a single slow database query or external API call creates a thread pool exhaustion that brings down an otherwise healthy service.

Graceful degradation—returning partial results or cached data when a dependency is unavailable—requires more design work, but is often the difference between a degraded user experience and a complete outage. Identifying which parts of the system can operate independently and building fallback paths is high-leverage resilience work.

05

Maintaining Delivery Velocity Through the Process

The organizational challenge of rearchitecture is sustaining feature delivery while the work is underway. Teams that suspend feature work to focus entirely on architecture typically face pressure to abandon the effort before it's complete. Teams that attempt rearchitecture entirely in parallel with full-speed feature delivery typically find neither progresses satisfactorily.

The most effective model dedicates a portion of each sprint—typically 20-30% of engineering capacity—to rearchitecture work, with the rest allocated to product delivery. This requires explicit agreement from product and business stakeholders that reliability investment is part of the engineering roadmap, not a tax on it.

Progress tracking should be concrete and visible: which failure modes have been addressed, what the incident rate and mean time to resolve look like before and after specific changes, what percentage of traffic is running on new vs. legacy paths. Abstract progress reporting ('we're modernizing the architecture') erodes stakeholder confidence. Specific metrics maintain it.

End