Don't Let Your System Go Down Completely: Designing Degraded Modes for Small Teams
When an upstream service fails, how can small teams keep the core path alive? Here's a framework for identifying, designing, and rehearsing degraded modes.
Last year we built a tool that depended on a third-party API. On a Friday afternoon, the API started returning 5xx errors. Our small team had no dedicated ops engineer; the monitoring alerts went off three times before we realized it was an upstream network issue. It took us four hours to flip the degradation switch—not because we didn't want to, but because we had never actually designed what the system should look like after degradation.
This article is about why small teams must design degraded modes in advance, and how. It's not a broad "high-availability architecture" topic; it's about concrete engineering decisions: switches, pages, timeouts.
Degradation is not "slowdown"—it's "a narrower path that still works"
Many teams think degradation means "the system becomes slow" or "a fallback when unavailable." But the essence of degradation is: when resources are constrained or dependencies fail, you proactively give up some features to protect the core value.
Here's an illustrative example (not from our experience, but common): a content community depends on a recommendation algorithm service. If that service fails, and the entire feed errors out, users see nothing. A degraded mode is to fall back to a simple time-sorted list. Recommendation quality drops, but users can still browse content. That's "a narrower path."
Small teams lack resources to build high-availability clusters for every dependency, but they can still turn "total outage" into "partial availability" through degraded-mode design.
Step 1: Map your dependencies and identify the critical ones
Degraded-mode design starts with knowing what you depend on. Many small teams only draw diagrams of their own services, not external dependencies. I suggest creating a dependency map:
- List all external services: APIs, databases, caches, third-party login, payment, SMS, object storage, etc.
- For each dependency, ask two questions:
- If it goes down, can my core path still work?
- If not, is there an alternative path?
- Mark "critical dependencies"—those without which the core value cannot be delivered.
For critical dependencies, you must design a degraded mode. For non-critical ones, at least have a graceful failure message.
A common misconception is treating the database as "always available." In reality, slow queries or connection pool exhaustion are more common than database downtime. The degraded mode doesn't have to be a replica; it can be limiting query depth, returning cached data, or rejecting non-core requests.
Step 2: Define trigger and recovery conditions for each degraded mode
Degradation is not a spontaneous decision; it's predefined: when to degrade, when to recover.
The worst kind of degradation I've seen is: the monitoring alert fires, but nobody knows whether to flip the switch because "let's wait and see." The result is usually users complaining loudly before you act.
I suggest defining three thresholds for each critical dependency:
- Warning threshold: e.g., error rate > 5% for 5 minutes. No need to degrade, but notify the owner.
- Degrade threshold: e.g., error rate > 20% for 2 minutes, or P99 latency > 3 seconds. Trigger degradation automatically or manually.
- Recovery threshold: e.g., error rate < 2% for 10 minutes, then allow rollback.
Don't just make up thresholds. Use a week of normal traffic data to calibrate. If you have no historical data, start with conservative values and adjust after the next incident.
Recovery conditions matter too. Many teams degrade but hesitate to recover because "it might fail again." Recovery conditions should be stricter than degradation conditions to avoid flapping.
Step 3: Design the degraded user experience in advance
Degradation isn't just a backend switch; it's frontend interaction. Users shouldn't see a 500 error page; they should see a message like "We've temporarily simplified some features."
Specifically:
- If degradation is invisible (e.g., feed goes from recommendations to time-sorted), add a subtle notice to avoid confusion about why content changed.
- If degradation makes certain features unavailable (e.g., payment disabled), clearly inform users and provide alternatives (e.g., "try again later" or "contact support").
- In degraded mode, preserve core user paths. For an e-commerce site, if search fails but category browsing works, ensure the category page remains accessible during degradation.
One easily overlooked point: tracking and monitoring during degraded mode. You need to distinguish "normal traffic" from "degraded traffic" to evaluate the impact after recovery. I suggest adding a flag in the degradation switch and logging it.
Step 4: Make the degradation switch a "concrete object," not just a boolean in code
Small teams often make the mistake of putting the degradation switch in code, controlled by a config file. Changing config requires a release, which requires a process, and by the time the process completes, the incident has lasted half an hour.
I suggest turning the degradation switch into an independent config center or a simple admin endpoint that can be modified dynamically without a release. Even a simple JSON config plus an internal admin page works.
If you're worried about misoperation, add a confirmation dialog, but ensure the time from confirmation to effect is within 1 minute.
Also, give degradation switches clear names and documentation. Don't wait until a failure, with several people arguing in a chat group about what a switch does.
Step 5: Rehearse regularly—don't let degraded-mode plans exist only in documents
The most reliable degraded-mode plan is one that someone has actually executed at least once. I suggest a quarterly "degradation drill": pick a non-peak time, force a degradation switch, observe the system, note problems, then restore.
The drill doesn't need to be complex. It could be:
- Manually cut off network access to a dependency (in a test environment).
- Observe whether degradation works as expected.
- Check if the user experience matches the design.
- Then restore and review.
The first drill often reveals many issues: a timeout set too short causing false degradation, a page breaking in degraded mode, an API still being called during degradation, etc. Finding these issues during a drill is a hundred times better than during a production incident.
Finally: Degradation is an engineering decision to accept imperfection
Small teams lack resources to build flawless high availability, but through degraded-mode design, we can turn "total outage" into "partial availability." This requires accepting that degraded mode lowers user experience, but it's better than nothing.
Every incident is a chance to calibrate. After an incident, ask: Did the degraded mode work as expected? Were the trigger conditions reasonable? Did the user experience match the design? Write these answers into your decision log and improve next time.
The essence of engineering is not pursuing perfection, but finding the least-bad path amid imperfection.
PaxLee