DEV Community

Cover image for Non-functional Application Requirements: Availability
Oluwasegun Adedigba for AWS Community Builders

Posted on Edited on

Non-functional Application Requirements: Availability

At 11:48 PM Pacific on October 19, 2025, two copies of the automation that manages DynamoDB's DNS in us-east-1 raced each other and left the Regional endpoint with an empty DNS record. DNS was back by 2:25 AM, but the outage wasn't over. EC2's host-management system depends on DynamoDB and fell into "congestive collapse". New instances came up before their network state had propagated, so load balancer health checks pulled healthy capacity out of service. AWS put the end at 2:20 PM, more than 14 hours in (AWS post-event summary, 2025).

One line in that summary deserves more attention. EC2 instances launched before the event "remained healthy" throughout. The systems that rode it out already had what they needed, and that idea runs through this article.

TL;DR

  • Promise an SLO with an error budget, never "zero downtime". At 99.9% over 30 days you get 43 minutes.
  • Your ceiling is the product of your hard dependencies. Copies only help if they fail independently.
  • Design for static stability: recover with capacity you already have, not a control plane mid-incident.
  • Let RTO and RPO pick your DR strategy, then rehearse it with AWS FIS.
  • For AI, treat 429s, 503s and model end-of-life as outages, and build a degradation ladder whose last rung needs no model.

What: availability, and the words people swap for it

Availability asks one question. Can users get the function they were promised, right now? Durability asks whether the data still exists, and resilience asks how well the system absorbs a hit and recovers. S3 Standard is designed for 99.99% availability and eleven nines of durability, so a bucket you can't reach for ten minutes hasn't lost a byte.

Four terms make it testable. An SLI is good events over total events, such as /orders requests answered without a 5xx in under a second. An SLO is the target for that ratio, say 99.9% over 30 days. An SLA is the contract with credits, set below your SLO. The error budget is what's left, so 3 million requests at 99.9% allow 3,000 failures (Google SRE Workbook).

Each nine divides your allowance by ten. Over 30 days, 99.9% gives you 43 minutes, 99.99% about 4, and 99.999% just 26 seconds.

Part 1 of this series promised "zero downtime, no matter what." I'm taking that back. What you can honestly promise is an SLO, a budget, and a plan for when the budget runs out.

Why: downtime has a price, and someone else often sets it

The October outage drew more than 17 million Downdetector reports, the most of any outage in 2025 (Ookla, December 2025). In Uptime Institute's 2025 survey, one in five operators said their last major outage cost over $1 million (Uptime Institute, Annual Outage Analysis 2026).

The number I'd put on a slide is from the same report. Over nine years of publicly reported outages, third parties such as cloud, telecom and colocation providers caused about two-thirds. Part of your availability is somebody else's architecture, so design as if it will fail.

The theory: four models for your next design review

1. Error budgets turn "be reliable" into numbers

While budget remains, you ship. Once it's spent, a written error budget policy kicks in, usually freezing risky changes until you're back within SLO. Alert on burn rate, how fast the budget is going, not on single errors. Google's SRE Workbook suggests paging when an hour eats 2% of a 30-day budget, a burn rate of 14.4.

2. Dependencies multiply, and copies only add if they're independent

Hard dependencies sit in series, so their availabilities multiply: A1 × A2 × … × An. Redundant copies sit in parallel, so all of them must fail at once: 1 − (1 − A)^n (AWS, Availability and Beyond whitepaper).

Let's put numbers on it with Harbor Works, the fictional plant in my open-source app Luso. Its line copilot runs API Gateway → Lambda → DynamoDB, then calls Amazon Bedrock. With the published SLAs (API Gateway and Lambda 99.95%, DynamoDB 99.99%, Bedrock 99.9%):

flowchart LR
  AG["API Gateway<br/>99.95%"] --> LA["Lambda<br/>99.95%"]
  LA --> DB["DynamoDB<br/>99.99%"]
  DB --> S["Serverless stack ceiling<br/>99.89%, about 48 min a month"]
  S --> BR["Bedrock<br/>99.9%"]
  BR --> C["Copilot ceiling<br/>99.79%, about 91 min a month"]

Each hard dependency in series lowers the ceiling, and the model call alone takes it from about 48 to about 91 minutes a month.

  • The serverless stack alone is 0.9995 × 0.9995 × 0.9999 = 99.89%, about 48 minutes a month.
  • Add Bedrock in series and you get 99.89% × 99.9% = 99.79%, about 91 minutes.
  • Copy everything to a second Region with its own Bedrock: 1 − (1 − 0.9979)² = 99.9996%, about 11 seconds.
  • Copy the stack but keep both Regions calling Bedrock in one Region: (1 − (1 − 0.9989)²) × 0.999 = 99.90%, about 43 minutes.

Compare the last two. Both run in two Regions for a similar bill, yet one shared dependency throws away three nines. A shared deploy pipeline or identity provider does the same, just more quietly.

flowchart LR
  U["User"] --> R53["Route 53 DNS"]
  R53 --> A["Region A stack<br/>99.89%"]
  R53 --> B["Region B stack<br/>99.89%"]
  A --> X["Shared dependency<br/>model Region, deploy pipeline, identity"]
  B --> X

Two Region stacks only count as parallel when nothing like the box on the right sits behind both of them.

Treat this as a ceiling, not a forecast. AWS calls the calculation "a rough order of magnitude." It's still the fastest way I know to catch a design that can't meet its SLO even on paper.

3. Decide how much is allowed to break at once

Blast radius is a choice you make in advance. AZs and Regions are boundaries AWS gives you. Cells are ones you build: isolated copies of the workload, each serving a slice of traffic by partition key. With 10 cells, one failure leaves 90% of requests untouched (AWS cell-based architecture guide). Keep the router "the thinnest possible layer", split on a natural key (for Harbor Works, the production line), and deploy one cell at a time.

flowchart LR
  T["Requests"] --> RT["Cell router<br/>thinnest possible layer<br/>splits on production line"]
  RT --> C1["Cell 1<br/>isolated copy of the workload"]
  RT --> C2["Cell 2<br/>failed"]
  RT --> C3["Cells 3 to 10<br/>isolated copies of the workload"]
  C1 --> OK["90% of requests untouched"]
  C3 --> OK
  C2 --> BAD["10% of requests affected"]

With ten cells, a failure stays inside one cell's slice of traffic instead of reaching every user.

4. Static stability: recover without asking for anything new

"In a statically stable design, the overall system keeps working even when a dependency becomes impaired" (Mike Furr, Amazon Builders' Library, 2019). The control plane creates and changes resources. The data plane keeps existing ones working, and it's simpler and built for higher availability. October was the textbook case: running instances kept serving while launches failed.

Three rules follow.

  • Pre-provision across AZs. If peak needs 10 instances, run 15 across three AZs so losing a zone needs no launches. AWS's DR whitepaper is blunt about why: "Auto Scaling is a control plane activity".
  • Fail over with data-plane actions. Route 53's control plane lives in us-east-1, while its data plane answers from over 200 points of presence. Health-check failover is data plane. Editing a record mid-incident isn't.
  • Make automatic failover cautious. After October, AWS is adding "a velocity control mechanism to limit the capacity a single NLB can remove." Keep your own health checks shallow, because a deep check on a shared dependency turns one failure into a cascade.
flowchart LR
  I["Incident<br/>an AZ or a dependency is impaired"] --> CP["Recovery that asks the control plane"]
  I --> DP["Recovery on the data plane"]
  CP --> CP1["Launch replacement instances<br/>Auto Scaling"]
  CP --> CP2["Edit a Route 53 record"]
  CP1 --> R1["Can fail mid-incident<br/>as launches did in October"]
  CP2 --> R1
  DP --> DP1["Pre-provisioned capacity<br/>15 instances across 3 AZs for a peak of 10"]
  DP --> DP2["Route 53 health-check failover"]
  DP1 --> R2["Keeps working with<br/>what already exists"]
  DP2 --> R2

A statically stable recovery only uses what's already running, so it works even while the control plane is struggling.

When: where availability enters the lifecycle

At design time, set one SLO per journey and mark every dependency hard or soft. In the build, put a timeout on every call, retries in one layer and idempotent writes. Before launch, run one fault-injection experiment per failure mode and one real failover. In operation, alarm on burn rate and drill on a calendar.

Where: the AWS pieces, and when not to use them

  • Route 53 health checks with failover records give you data-plane Regional failover in about 90 seconds at the default settings, plus the 60-second TTL on ALB DNS names. A check on an AWS endpoint costs $0.50 a month, plus $1 per optional feature such as HTTPS.
  • Amazon Application Recovery Controller (ARC) zonal shift and autoshift move traffic off an impaired AZ. Autoshift needs weekly practice runs of about 30 minutes, so pre-scale the other AZs first. ARC Region switch runs multi-Region recovery plans with "a data plane in each AWS Region". ARC readiness check closed to new customers on April 30, 2026, and AWS points you to Region switch instead.
  • Aurora Global Database has RPO 0 on a planned switchover, but an unplanned failover loses seconds of data.
  • DynamoDB global tables (99.999% SLA) in default MREC mode have an RPO of a few seconds. MRSC gets RPO 0 but needs exactly three Regions and drops TTL, local secondary indexes and transactions.
  • AWS FIS runs the experiments, and its scenario library includes AZ Availability: Power Interruption and Cross-Region: Connectivity.

For experiments inside Kubernetes, LitmusChaos (CNCF Incubating) fits better, and Sloth generates Prometheus SLO rules from a short spec. For FIS across many accounts, see my Conquering Cloud Chaos (2024).

Decide: how far to go, and what it costs elsewhere

Start with two numbers per journey. RTO is the longest acceptable gap before service comes back. RPO is how much data, measured in time, you can afford to lose. Write both down before anyone says "multi-Region", then pick the cheapest of AWS's four strategies that meets them (Well-Architected REL13-BP02).

Four disaster recovery strategies, from cheapest to most expensive. Backup and restore loses hours of data and takes up to a day to recover. Pilot light keeps data replicas with compute switched off, losing minutes and recovering in tens of minutes. Warm standby runs a scaled-down full stack, losing seconds and recovering in minutes. Active-active serves live traffic from every Region for near-zero loss, at the highest cost and complexity.

Strategy RPO / RTO (per AWS) What runs in the recovery Region Choose it when
Backup and restore Hours / 24 h or less Backups only Internal tools, or data you can rebuild
Pilot light Minutes / tens of minutes Data replicas, compute switched off Data must be current, but an hour down is survivable
Warm standby Seconds / minutes A scaled-down full stack Customer-facing journeys
Multi-site active-active Near zero / potentially zero Full stack taking live traffic Revenue stops when you stop, and the team can run it

My rule of thumb is multi-AZ in one Region for SLOs up to about 99.95%, and multi-Region at 99.99% or when a regulator wants a separate site. Bad writes replicate too, so even active-active needs point-in-time backups you've actually restored.

Every extra nine gets paid for somewhere else in this series.

Other NFR What more availability does to it
Security More Regions mean more IAM, keys and secrets to keep in sync
Compliance Replicas and cross-Region inference move where data lives and is processed
Accessibility Fallback and "degraded" pages are often the least-tested UI you ship
Performance MRSC and synchronous replication add write latency
Scalability Static stability pre-buys capacity instead of scaling on demand
Maintainability Each Region is another deploy target, and drift is what breaks failovers
Cost Warm standby and active-active mean paying for a second data tier that mostly waits
Sustainability Idle standby capacity draws power every hour it waits

Hands-on: data-plane failover, then break it on purpose

Harbor Works serves from eu-west-1 with a warm standby in eu-central-1, both behind existing ALBs. Everything exists ahead of time, so failover makes no control-plane calls.

Checked against the Route 53 HealthCheckConfig API reference and the Terraform AWS provider docs on Oct 29, 2026. terraform validate passes on Terraform 1.17.1 with AWS provider 6.71.0. Not deployed, so test it in a sandbox account first.

terraform {
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 6.0" }
  }
}

variable "zone_name" { default = "example.com" }
variable "record_name" { default = "api.example.com" }
variable "primary_alb" { default = "api-euw1" } # existing ALB names
variable "secondary_alb" { default = "api-euc1" }

provider "aws" {
  alias  = "primary"
  region = "eu-west-1"
}
provider "aws" {
  alias  = "secondary"
  region = "eu-central-1"
}

data "aws_route53_zone" "this" {
  provider = aws.primary
  name     = var.zone_name
}
data "aws_lb" "primary" {
  provider = aws.primary
  name     = var.primary_alb
}
data "aws_lb" "secondary" {
  provider = aws.secondary
  name     = var.secondary_alb
}

# Route 53 checkers probe this from several locations. No API call happens at failover time.
resource "aws_route53_health_check" "primary" {
  provider          = aws.primary
  fqdn              = data.aws_lb.primary.dns_name
  type              = "HTTPS"
  port              = 443
  resource_path     = "/healthz"                              # shallow: this Region's own deps only
  request_interval  = 30                                      # 10 is faster, costs extra, can't change later
  failure_threshold = 3                                       # about 90 s to mark unhealthy
  regions           = ["eu-west-1", "us-east-1", "us-west-2"] # API minimum is 3
  tags              = { Name = "api-primary-euw1" }
}

resource "aws_route53_record" "primary" {
  provider        = aws.primary
  zone_id         = data.aws_route53_zone.this.zone_id
  name            = var.record_name
  type            = "A"
  set_identifier  = "primary-euw1"
  health_check_id = aws_route53_health_check.primary.id
  failover_routing_policy { type = "PRIMARY" }
  alias {
    name                   = data.aws_lb.primary.dns_name
    zone_id                = data.aws_lb.primary.zone_id
    evaluate_target_health = true # also fail over if the ALB has no healthy targets
  }
}

resource "aws_route53_record" "secondary" {
  provider       = aws.primary
  zone_id        = data.aws_route53_zone.this.zone_id
  name           = var.record_name
  type           = "A"
  set_identifier = "secondary-euc1"
  failover_routing_policy { type = "SECONDARY" }
  alias {
    name                   = data.aws_lb.secondary.dns_name
    zone_id                = data.aws_lb.secondary.zone_id
    evaluate_target_health = true
  }
}
Enter fullscreen mode Exit fullscreen mode

One Route 53 behavior surprises people: if both records are unhealthy, it returns the primary anyway.

sequenceDiagram
  participant HC as Health checkers
  participant P as Primary ALB
  participant DNS as Route 53 DNS
  participant C as Client
  HC->>P: GET /healthz every 30 s
  P-->>HC: 3 consecutive failures
  HC->>DNS: primary marked unhealthy
  C->>DNS: resolve api.example.com
  DNS-->>C: secondary ALB, TTL 60 s
  Note over C,DNS: Clients with a cached answer keep hitting the primary until the TTL expires

Budget about 90 seconds to detect plus 60 seconds of cached DNS, and put that in your RTO.

Now rehearse October in miniature. This experiment cuts the app subnets off from the Regional DynamoDB endpoint for 10 minutes, and stops early if the SLO fast-burn alarm fires.

Checked against the AWS FIS actions reference on Oct 29, 2026, and validated against the CreateExperimentTemplate model in boto3 1.43.121. Not deployed, so test it in a sandbox account first.

{
  "description": "Game day: app subnets lose the Regional DynamoDB endpoint for 10 minutes",
  "targets": {
    "appSubnets": {
      "resourceType": "aws:ec2:subnet",
      "resourceTags": { "tier": "app" },
      "selectionMode": "ALL"
    }
  },
  "actions": {
    "denyDynamoDB": {
      "actionId": "aws:network:disrupt-connectivity",
      "parameters": { "scope": "dynamodb", "duration": "PT10M" },
      "targets": { "Subnets": "appSubnets" }
    }
  },
  "stopConditions": [
    {
      "source": "aws:cloudwatch:alarm",
      "value": "arn:aws:cloudwatch:eu-west-1:111122223333:alarm:orders-slo-fast-burn"
    }
  ],
  "roleArn": "arn:aws:iam::111122223333:role/fis-experiment-role",
  "tags": { "Name": "dynamodb-endpoint-loss" }
}
Enter fullscreen mode Exit fullscreen mode

Attach the AWSFaultInjectionSimulatorNetworkAccess managed policy to the role, then run aws fis create-experiment-template --cli-input-json file://dynamodb-endpoint-loss.json. A passing run is boring. /healthz stays green and users get a degraded answer, not an error.

Availability in the age of AI

AI as a workload

A model is a dependency with an SLA, a quota, a lifecycle, and one failure mode your load balancer can't see: a confident wrong answer with HTTP 200.

Throttling arrives long before Region outages do. Bedrock's ThrottlingException (429) means you hit your account quota, which an increase fixes. Its 503 means the service is short of capacity, "not related to your account-level quotas". Back off with jitter and retry in one layer only, since three layers of three attempts can hit a struggling dependency 27 times. Keep maxTokens near your real output size, because Bedrock reserves it against your tokens-per-minute quota at request start. Then add one circuit breaker per model.

Put the model in your math. The Bedrock SLA promises 99.9% for the service, but the Reserved tier "targets 99.5% uptime for model response" (AWS, November 2025). Plan on the lower, model-level number. In series with Harbor Works' 99.89% stack, the ceiling drops to 99.39%, about 4.4 hours a month, and no Lambda tuning gets you back above it.

Cross-Region inference trades residency for capacity. A geographic profile such as eu. can process your prompt in any Region in that geography, and a global. profile in any supported commercial Region, for about 10% less. Your SCPs must allow every destination, and CloudTrail records where each request ran in additionalEventData.inferenceRegion. Just don't call it DR. AWS says it "does not, however, function as a disaster recovery or automatic failover system" (AWS Machine Learning Blog, February 2026).

Build a degradation ladder, where each rung depends on less than the one above.

flowchart TB
  Q["User request"] --> R1{"Rung 1<br/>primary model"}
  R1 -->|"ok"| A["Full answer"]
  R1 -->|"429, 503, timeout or breaker open"| R2{"Rung 2<br/>smaller model, other provider"}
  R2 -->|"ok"| B["Shorter answer"]
  R2 -->|"fails"| R3{"Rung 3<br/>cached answer"}
  R3 -->|"hit"| C["Last good answer"]
  R3 -->|"miss"| R4["Rung 4<br/>non-AI path"]

Rungs 1 and 2 both depend on Bedrock, so only rungs 3 and 4 survive a Bedrock-wide problem.

So the parallel math won't rescue you. Two independent 99.5% models would give 99.9975%, but these share an endpoint, a Region and an account. Rarely used fallbacks also tend to fail when needed (Jacob Gabrielson, Amazon Builders' Library, 2019), so send a trickle of live traffic down rung 2 and test rung 4 synthetically.

Checked against the Bedrock Converse API reference and the Claude Sonnet 4.6 and Nova 2 Lite model cards on Oct 29, 2026. Unit-tested with botocore's Stubber on boto3 1.43.121 for the throttle, 503, model-timeout and validation-error paths. Not deployed, so test it in a sandbox account first.

import time
import boto3
from botocore.config import Config
from botocore.exceptions import ClientError, ConnectTimeoutError, ReadTimeoutError

# Standard mode: exponential backoff with jitter, plus a retry quota.
# Keep attempts low. The ladder below is the real fallback.
cfg = Config(retries={"mode": "standard", "max_attempts": 2},
             connect_timeout=2, read_timeout=20)
brt = boto3.client("bedrock-runtime", region_name="eu-west-1", config=cfg)

# Pinned IDs. The eu. geographic profiles keep processing inside the EU.
RUNGS = [
    "eu.anthropic.claude-sonnet-4-6",  # rung 1: primary model
    "eu.amazon.nova-2-lite-v1:0",      # rung 2: smaller model, different provider
]
RETRYABLE = {"ThrottlingException", "ServiceUnavailableException",
             "ModelNotReadyException", "ModelTimeoutException",
             "InternalServerException"}


class Breaker:
    """Opens after N straight failures, lets a trial call through after cooldown."""
    def __init__(self, threshold=5, cooldown=30):
        self.threshold, self.cooldown = threshold, cooldown
        self.failures, self.opened_at = 0, None

    def allow(self):
        if self.opened_at is None:
            return True
        return time.monotonic() - self.opened_at >= self.cooldown  # half-open

    def record(self, ok):
        if ok:
            self.failures, self.opened_at = 0, None
        else:
            self.failures += 1
            if self.failures >= self.threshold:
                self.opened_at = time.monotonic()


breakers = {m: Breaker() for m in RUNGS}
cache = {}  # stand-in: use DynamoDB or ElastiCache, keyed by a normalized prompt


def answer(prompt: str) -> dict:
    for model in RUNGS:
        if not breakers[model].allow():
            continue                                 # breaker open: skip this rung fast
        try:
            r = brt.converse(
                modelId=model,
                messages=[{"role": "user", "content": [{"text": prompt}]}],
                inferenceConfig={"maxTokens": 512},  # reserved against TPM at request start
            )
            breakers[model].record(True)
            text = r["output"]["message"]["content"][0]["text"]
            cache[prompt] = text
            return {"rung": model, "text": text}
        except ClientError as e:
            if e.response["Error"]["Code"] not in RETRYABLE:
                raise                                # a 400 or 403 is a bug, not an outage
            breakers[model].record(False)
        except (ConnectTimeoutError, ReadTimeoutError):
            breakers[model].record(False)
    if prompt in cache:                              # rung 3: last good answer
        return {"rung": "cache", "text": cache[prompt]}
    return {"rung": "non-ai",                        # rung 4: the product still works
            "text": "Line summaries are busy. Here is the shift log for this line."}
Enter fullscreen mode Exit fullscreen mode

Pin model versions and watch the calendar. Bedrock models go Active → Legacy → End-of-Life. After EOL "requests made to it will fail", and "migration will not happen automatically" (Bedrock model lifecycle). Legacy periods run six months, sometimes only 45 days. Claude Sonnet 4.5, for example, is Legacy with EOL on April 8, 2027. Pin exact IDs, pull modelLifecycle.endOfLifeTime from GetFoundationModel into an inventory, and alarm 90 days out.

Run evals as synthetic health checks. A model can get worse without a single 5xx. Run a small golden set every few minutes, publish the pass rate as a CloudWatch metric, and alarm on it. A Route 53 CLOUDWATCH_METRIC health check can then fail over on quality (same-account, standard-resolution alarms only).

Give agents durable state. An agent that forgets everything at step 31 of 40 is down. Step Functions Standard workflows and Lambda durable functions both checkpoint progress and run for up to a year. AgentCore Runtime microVM sessions default to an 8-hour lifetime and a 15-minute idle timeout, so keep state outside them, and make tool calls idempotent so a replay can't order twice.

Vector stores need an RTO and RPO too. Either replicate the index, or replicate the sources and re-embed, which is cheap to keep ready but slow to run. OpenSearch Service offers active-passive cross-cluster replication. DynamoDB vector search (GA August 2026) replicates the index definition with global tables, but index contents propagate asynchronously, even under MRSC. The S3 Vectors guide documents no replication or backup as of October 2026, so replicate the source documents and count re-embedding in your RTO. Only replicate into Regions your residency rules allow.

Still immature: FIS has no Bedrock fault action, so simulate 429s in your client or a gateway. For provider-neutral fallback, the open-source LiteLLM proxy handles retries, cooldowns and cross-provider failover.

AI as an engineering aid

AI tools are good at the tedious parts, like drafting a dependency map from your IaC or turning a runbook into an FIS template. AWS DevOps Agent (GA March 2026) correlates telemetry, code and deployments during incidents, though its "hours to minutes" MTTR claim comes with no published measurement.

They also make things up, like a Bedrock fault action that doesn't exist, or two copies in one AZ treated as independent. Validate generated templates against the API model (botocore's ParamValidator checked the FIS template above), refuse experiments without a stop condition, and keep a human approval on any production failover an agent proposes.

Who: owners and the shared responsibility split

Activity Product owner Architect Developers SRE / platform
SLOs and error budget policy A R C R
Dependency map and DR strategy C A, R C C
Timeouts, retries, idempotency, degradation ladder I C A, R C
Experiments, drills, burn-rate alarms I C C A, R

R = responsible, A = accountable, C = consulted, I = informed.

AWS owns "resiliency of the cloud" and you own "resiliency in the cloud". Your share depends on the compute.

  • EC2. You place instances across AZs, keep spare capacity and run the self-healing.
  • Containers. You spread tasks or pods across AZ subnets and own health checks. Fargate takes the hosts off your hands.
  • Lambda. AWS runs functions across AZs. For VPC-attached functions, picking multi-AZ subnets is on you.
  • Managed AI. The provider sets the lifecycle and AWS serves the model. Quotas, profiles, the ladder, EOL tracking and evals are yours.

Monday checklist

  • [ ] Write one SLO for your most important journey and get the error budget policy signed.
  • [ ] Multiply your hard dependencies. If the product is below your SLO, that gap is your backlog.
  • [ ] Replace every recovery step that calls a control plane with pre-provisioned capacity or a health-check switch.
  • [ ] Strip shared dependencies out of /healthz.
  • [ ] Write down RTO and RPO per journey, then pick the DR strategy from the table.
  • [ ] Run the DynamoDB endpoint-loss experiment in a sandbox account.
  • [ ] Cut your busiest path down to one retry layer.
  • [ ] List every production model ID with its EOL date.
  • [ ] Decide, and test, what your AI feature shows when no model answers.

Conclusion

Availability is arithmetic before it's architecture. Multiply your dependencies to find your ceiling. Ask whether your copies fail independently to learn whether your redundancy is real. Ask whether recovery needs a control plane to learn whether it'll work on the day.

AI doesn't change those rules. It adds a dependency with a quota, a lifecycle and a failure mode that returns HTTP 200. Put it in the math, give it a ladder, and make sure the last rung needs no model.

Nines are arithmetic. Recovery is rehearsal.


Series: Non-functional Application Requirements

  1. Non-functional Application Requirements: An Introduction
  2. Non-functional Application Requirements: Security
  3. Non-functional Application Requirements: Compliance
  4. Accessibility
  5. Performance
  6. Availability
  7. Scalability (coming soon)
  8. Maintainability (coming soon)
  9. Cost (coming soon)
  10. Sustainability (coming soon)

Cover image: AWS re:Invent 2023 – Keynote with Dr. Werner Vogels

Next up, Scalability. A cost-benefit look at how you grow, where tokens per minute become a capacity unit as real as vCPUs.

Sources

Top comments (0)