Skip to content

Cookies on Jobverse

We use cookies that are needed to sign you in and keep the site working. With your permission we’d also use an analytics cookie to count job views, and Google advertising cookies to show ads. The site works the same whatever you choose. Read the Cookie Policy.

Jobverse
Sign in

Site Reliability Engineer Top 100 Interview Questions

100 site reliability engineer interview questions with structured answers, comparison tables, flow diagrams, worked calculations and runnable Python and shell. Covers service level objectives and error budgets, alerting, incident response, capacity and queueing, resilience patterns, observability, systems debugging and disaster recovery, from the basics to real outages.

100 questions

Questions 61-80 of 100

  1. Q61.How do you prevent outages from expired certificates?

    Expired certificates break connections instantly and completely, often for services nobody has touched in months. Prevention is almost entirely about inventory and automation.

    1. Inventory every certificate: public endpoints, internal services, load balancers, client certificates and certificates embedded in devices or partner integrations.
    2. Automate renewal and deployment, with short lifetimes, so renewal happens often and is well practiced.
    3. Monitor expiry from the outside: probes that connect to each endpoint and read the certificate actually served, since renewed files that were never reloaded still expire.
    4. Alert early: for example, a ticket 30 days before expiry and a page at 7 days for anything not renewed automatically.
    5. Watch the whole chain: intermediate certificates and trusted roots can expire too.
    6. Test reloads: confirm services pick up new certificates without restarts, or schedule rolling restarts after renewal.

    After an expiry incident, add the missed certificate to the inventory and probes, and ask why automation did not cover it.

    Example

    A small check sorts certificates by expiry and flags those needing action.

    from datetime import date
    from operator import itemgetter
    
    certificates = {
        "api.example.internal": date(2026, 4, 2),
        "pay.example.internal": date(2026, 3, 20),
        "shop.example.internal": date(2026, 6, 30),
    }
    today = date(2026, 3, 10)
    
    for host, expires in sorted(certificates.items(), key=itemgetter(1)):
        days = (expires - today).days
        status = "renew now" if days < 14 else "renew soon" if days < 30 else "ok"
        print(f"{host} expires in {days} days: {status}")
    # Output:
    # pay.example.internal expires in 10 days: renew now
    # api.example.internal expires in 23 days: renew soon
    # shop.example.internal expires in 112 days: ok
  2. Q62.What is clock skew, and how can it break distributed systems?

    Clock skew is the difference between the clocks of different machines. Even with time synchronization, clocks drift apart by milliseconds or more, and a host with a broken synchronization setup can be off by minutes.

    Token validation failuresWrong ordering of eventsLost updatesBroken leases and locks
    Tokens rejected as "not yet valid" by a server whose clock is aheadLogs or writes from two hosts appear in the wrong orderA "last write wins" rule keeps an older write with a later timestampA host believes its lease is valid after it has expired elsewhere
    Fix: allow a small tolerance when checking time-based claimsFix: use sequence numbers or logical clocks for orderingFix: use version numbers or the data store's own orderingFix: use fencing tokens and conservative lease margins

    Practices:

    1. Run time synchronization on every host, with monitoring of each host's offset and alerts above a threshold.
    2. Use monotonic clocks to measure durations within a process.
    3. Never rely on wall-clock time from different machines to decide correctness.
    4. Include clock offset in incident checklists when errors involve tokens, expiry or ordering.
  3. Q63.How do you debug intermittent connection resets between services?

    Intermittent resets usually come from something closing connections that the other side still thinks are open, from devices along the path or from overloaded endpoints. Find out who sent the reset and when, then match it to a timeout or limit.

    Common causes:

    • Idle timeouts: a load balancer, firewall or address translation device drops idle connections, and the next request on them fails.
    • Keep-alive mismatch: a server closes idle connections sooner than the client expects.
    • Connection tracking limits: a host or network device runs out of entries and drops new or old connections.
    • Overload: a server's listen queue is full, so it refuses or resets new connections.
    • Path problems: packet size mismatches along a route, which break large transfers but not small ones.

    How to investigate:

    1. Correlate resets with timing: after idle periods, at high load or for large payloads.
    2. Capture traffic on both ends for a sample of failures to see which side sent the reset.
    3. Compare idle timeouts at every hop, and make clients close idle connections first.
    4. Check device and kernel counters for dropped connections.

    Retrying idempotent requests once on a reset hides the residual cases, but fix the timeout mismatch first.

  4. Q64.How do you run databases reliably with replication, failover and connection limits?

    Most database incidents involve replication, failover or connection exhaustion. Plan and test each one explicitly, since database failures affect every service above them.

    sequenceDiagram
      participant A as Application
      participant P as Primary
      participant R as Replica
      participant M as Failover manager
      A->>P: Writes
      P->>R: Replicate changes
      M->>P: Health check fails
      M->>R: Confirm replica is caught up
      M->>R: Promote to primary
      M->>A: Update endpoint
      A->>R: Writes resume

    Replication:

    • Monitor replication lag, and alert when it exceeds what reads from replicas can tolerate.
    • Send reads that must see the latest data to the primary.

    Failover:

    • Automate it, with checks that prevent two primaries from accepting writes at the same time.
    • Test it regularly, and measure how long applications take to reconnect.

    Connections:

    • Limit pool sizes per instance, so the total stays under the database's maximum even at full scale-out.
    • Use a connection proxy when there are many application instances.

    Other essentials: query timeouts, slow-query monitoring, capacity planning for storage and tested backups.

  5. Q65.A database is running out of connections. What do you do?

    Stabilize first, then find whether the cause is too many clients, connections held too long or leaked connections, and fix the sizing so it cannot happen again at full scale.

    Immediate steps:

    1. Identify which applications hold the most connections, and how many are idle versus active.
    2. Kill clearly idle or stuck sessions if safe.
    3. Stop the scale-out that is adding connections, or reduce pool sizes on the largest consumers.

    Common causes:

    • Pool sizes multiplied by instance count: autoscaling adds instances, each with a full pool.
    • Long transactions or slow queries holding connections.
    • Leaks: code paths that do not return connections to the pool.
    • Retry storms opening new connections during a slowdown.

    Lasting fixes:

    1. Size pools from the database's limit: maximum connections, minus a reserve for administration, divided by the maximum instance count.
    2. Add a connection proxy that multiplexes many client connections onto fewer database connections.
    3. Set statement and idle-in-transaction timeouts.
    4. Alert on connection usage as a share of the maximum.

    Example

    A database allows 500 connections. Forty application instances each have a pool of 20.

    1. Current maximum: 40 × 20 = 800 connections, already above the limit.
    2. Autoscaling can reach 60 instances, which would need 1,200.
    3. Reserving 20 connections for administration leaves 480, so each of 60 instances can have 480 / 60 = 8.
    4. Measurements show each instance rarely uses more than 6 at once, so a pool of 8 is enough, and a proxy can be added later if instance counts grow further.
  6. Q66.How do you make a queue-based system reliable?

    Queues decouple producers from consumers and absorb bursts, but they can hide failures: messages pile up, poison messages block progress, and duplicates arrive after retries. Reliability comes from designing consumers for these cases.

    flowchart LR
      A["Producer"] --> B["Main queue"]
      B --> C["Consumer"]
      C -->|Success| D["Acknowledge and delete"]
      C -->|Failure| E{"Attempts below limit?"}
      E -->|Yes| B
      E -->|No| F["Dead-letter queue"]
      F --> G["Alert and investigate"]

    Key practices:

    1. Idempotent consumers: most queues deliver at least once, so processing the same message twice must be safe, for example by recording processed message IDs.
    2. Acknowledge after processing, not before, so crashes do not lose messages.
    3. Visibility timeouts longer than processing time, so messages are not redelivered while still being handled.
    4. Retry limits and a dead-letter queue, so one bad message cannot block the queue forever.
    5. Backpressure: limit queue size or producer rate, so a consumer outage does not grow an unbounded backlog.
    6. Monitoring: age of the oldest message, depth, processing rate and dead-letter arrivals.
  7. Q67.How do you monitor a queue's health?

    The most useful signal is the age of the oldest unprocessed message, because it directly measures how late work is. Queue depth alone can mislead: a deep queue that is draining fast may be fine, while a shallow queue with a stuck message is not.

    Signals to track:

    • Age of oldest message, compared with the freshness the business needs.
    • Arrival rate and processing rate: if arrivals exceed processing, the backlog grows.
    • Depth, as context for the other signals.
    • Dead-letter arrivals, which indicate messages that cannot be processed.
    • Consumer errors and retries.

    Alerting:

    1. Page when the oldest message age threatens the objective, such as orders older than ten minutes.
    2. Ticket when arrivals exceed processing for a sustained period, before age becomes a problem.
    3. Alert on any growth in the dead-letter queue for critical flows.

    Estimating recovery: the time to clear a backlog is the backlog divided by the difference between processing and arrival rates, which tells responders whether adding consumers will help in time.

    Example

    Messages arrive at 600 per second, but consumers process only 500 per second after a slowdown.

    1. The backlog grows by 100 messages per second, reaching 360,000 after one hour.
    2. At 500 per second, the newest message waits about 360,000 / 500 = 720 seconds, 12 minutes.
    3. Scaling consumers to 800 per second drains the backlog at 800 − 600 = 200 per second.
    4. Clearing it takes 360,000 / 200 = 1,800 seconds, about 30 minutes.
  8. Q68.How do you run a large data migration or backfill in production safely?

    Treat the migration as a long-running production workload with its own capacity limits, monitoring and rollback plan, so it never competes with user traffic in ways that break objectives.

    1. Rehearse on a copy of production data, measuring speed and checking results.
    2. Make it resumable: process in batches with checkpoints, so a failure restarts from the last batch, not the beginning.
    3. Make it idempotent: rerunning a batch gives the same result, with no duplicates.
    4. Throttle: limit rows per second, and pause automatically when database latency, replication lag or error rates rise.
    5. Run during quieter periods when possible, but design for it to be safe at any time.
    6. Validate continuously: compare counts and checksums between source and target, and sample records.
    7. Keep the old path working until the migration is verified, with a clear switch-over and switch-back plan.
    8. Communicate: tell on-call teams what is running, its expected impact and how to pause it.

    The most common failure is overloading a shared database, so the throttle and its automatic pause are the most important parts.

  9. Q69.How do you keep backups trustworthy?

    A backup is only as good as your last successful restore. Trustworthy backups are automated, isolated from the systems they protect, regularly restored and monitored.

    Practices:

    1. Automate backups for every critical data store, with schedules that meet the recovery point objective.
    2. Isolate copies: store backups in a separate account or location, with write-once settings where possible, so a mistake or an attacker who reaches production cannot delete them.
    3. Encrypt backups and manage the keys separately, making sure the keys themselves are recoverable.
    4. Restore regularly and automatically: restore into an isolated environment, run integrity checks and compare record counts with production.
    5. Measure restore time against the recovery time objective, including the time to download large backups.
    6. Monitor: alert on failed, late or unusually small backups.
    7. Cover everything needed to recover: configuration, secrets, schemas and infrastructure definitions, not just the data.

    Common surprises discovered in restore tests: missing tables, backups that stopped months ago, restores far slower than expected and lost encryption keys.

  10. Q70.How do you manage reliability risks from third-party services?

    Third-party services, such as payment providers, messaging services and identity providers, are dependencies you cannot fix yourself. Reduce how much a failure hurts, and make sure you know about failures quickly.

    1. Classify each dependency as hard or soft for every user journey.
    2. Know their commitments: published objectives, support terms and status channels.
    3. Monitor them yourself: track latency and error rates of your calls, rather than relying on their status page.
    4. Protect yourself: timeouts, retries with backoff, circuit breakers and concurrency limits on every call.
    5. Degrade gracefully: queue work for later, such as sending emails when the provider recovers, or hide optional features.
    6. Have alternatives for critical paths: a second payment provider or a fallback login method, tested regularly.
    7. Watch quotas and rate limits, which can be reached during your own traffic peaks.
    8. Plan communications: templates for telling users when a provider's outage affects them.

    Include third-party failures in game days, since teams often discover hidden hard dependencies only when a provider goes down.

  11. Q71.How do you prioritize reliability risks across a system?

    Keep a risk register that estimates, for each known risk, how often it might happen and how much user impact it would cause, then rank by expected impact and the cost of reducing it.

    Bad configuration pushLoss of an availability zoneExpired certificate
    About 4 times per yearAbout 0.3 times per yearAbout 0.5 times per year
    About 45 minutes of impact each timeAbout 300 minutes of impact each timeAbout 120 minutes of impact each time
    Expected impact: 180 minutes per yearExpected impact: 90 minutes per yearExpected impact: 60 minutes per year

    How to build the register:

    1. Collect risks from incident history, readiness reviews, game days and team knowledge.
    2. Estimate frequency and impact roughly; precision matters less than consistency.
    3. Compare expected impact with the error budget, to see which risks could consume most of it.
    4. Estimate the cost of each mitigation, and prioritize the best impact reduction per unit of effort.

    Keep it current: update estimates after incidents and mitigations, and review the register quarterly with engineering leadership.

    Example

    Using the risks in the table, the team has engineering time for one project this quarter.

    1. Staged configuration rollout with automatic validation would cut the configuration risk from 180 to about 30 expected minutes per year, saving 150 minutes.
    2. Automated certificate renewal would save about 60 minutes per year.
    3. The configuration project saves more than twice as much expected impact, so it goes first, with certificates scheduled next.
  12. Q72.How do you estimate the cost of downtime for a service?

    Estimate the direct revenue lost during the outage, subtract what customers recover later, then add indirect costs such as support, compensation and long-term customer loss. The number turns reliability debates into business decisions.

    Components:

    1. Lost transactions: revenue per minute at the time of day the outage happens, since peaks cost far more than quiet hours.
    2. Recovery: the share of customers who come back and complete their purchase later.
    3. Contract penalties: service credits owed under agreements.
    4. Operational cost: responder time, support contacts and refunds.
    5. Reputation and churn: harder to measure, but estimated from customer behavior after past incidents.

    How to use it:

    • Compare the expected cost of downtime with the cost of reliability investments, such as a second region.
    • Choose objectives that reflect the business value of each journey.
    • Prioritize incident follow-ups by their potential cost.

    Use ranges rather than single numbers, and update them with data from real incidents.

    Example

    A checkout handles 12,000,000 rupees of orders per day, and its peak hour carries 10% of that.

    1. Peak revenue rate: 1,200,000 rupees per hour, or 20,000 per minute.
    2. A 30-minute outage at peak puts 600,000 rupees of orders at risk.
    3. If 40% of affected customers return and buy later, the lost revenue is 600,000 × 0.6 = 360,000 rupees.
    4. Support contacts and service credits add an estimated 50,000 rupees, giving roughly 410,000 rupees, before any long-term customer loss.
  13. Q73.How do you forecast when a resource will run out, and alert in time?

    Fit a trend to recent usage, project when it will reach capacity and alert on the time remaining rather than on a fixed usage percentage. A disk at 90% that grows slowly is less urgent than one at 60% that fills in two days.

    How to do it:

    1. Collect usage over a window long enough to show the trend but recent enough to reflect current behavior, such as the last one or two weeks.
    2. Fit a simple trend, such as a straight line, or use the recent maximum growth rate for a cautious estimate.
    3. Project time to exhaustion: remaining capacity divided by the growth rate.
    4. Alert by lead time: a ticket when less than the time needed to add capacity remains, such as 14 days, and a page when only hours remain.

    Pitfalls:

    • Sudden step changes, such as a new log level, break trends; use a short window too.
    • Cyclical patterns, such as nightly batch writes, need daily peaks rather than raw samples.
    • Some resources, such as connection limits, fail suddenly rather than gradually.

    Example

    Disk usage is sampled daily, in gigabytes, on a 2,000 GB volume.

    import statistics
    
    days = [0, 1, 2, 3, 4, 5, 6]
    used_gb = [1_400, 1_424, 1_451, 1_474, 1_500, 1_526, 1_549]
    capacity_gb = 2_000
    
    slope, _ = statistics.linear_regression(days, used_gb)
    days_left = (capacity_gb - used_gb[-1]) / slope
    
    print(round(slope, 1))  # 25.0
    print(round(days_left, 1))  # 18.0

    With about 18 days left and a 14-day lead time for new storage, a ticket should open within the next few days.

  14. Q74.How would you compute a latency objective's compliance from an access log with a shell script?

    Read each request line, count it as good if it succeeded and was faster than the latency threshold, then divide good requests by all requests. A short script is useful for quick checks during incidents or for services without a metrics pipeline yet.

    Steps:

    1. Decide the fields: here the third field is the status code and the fourth is latency in milliseconds.
    2. Define a good request: status below 500 and latency at or below the threshold.
    3. Count good and total requests, and print the ratio.
    4. Compare it with the objective.

    Making it reliable:

    • Exclude invalid traffic, such as health checks, if the objective does not include it.
    • Process logs in time windows matching the objective, such as one day.
    • For production reporting, move the same logic into the metrics pipeline, so results are continuous and consistent.

    Treating client errors such as 404 as good is a deliberate choice: the service answered correctly, so they usually do not count against reliability.

    Example

    Ten requests are checked against a 300 ms threshold and a 95% objective.

    cat > access.log <<'EOF'
    GET /checkout 200 120
    GET /checkout 200 340
    POST /checkout 500 80
    GET /search 200 95
    GET /checkout 503 1200
    GET /search 200 410
    GET /checkout 200 150
    GET /search 200 90
    GET /checkout 200 210
    GET /search 404 60
    EOF
    
    awk -v threshold=300 -v objective=95 '
      { total++ }
      $3 < 500 && $4 <= threshold { good++ }
      END {
        sli = 100 * good / total
        printf "good=%d total=%d sli=%.1f%%\n", good, total, sli
        print (sli >= objective ? "objective met" : "objective missed")
      }
    ' access.log
    # Output:
    # good=6 total=10 sli=60.0%
    # objective missed
  15. Q75.How would you produce a daily objective compliance and error budget report in Python?

    Group requests by day, classify each as good or bad using the objective's definition, report each day's ratio and compute how much of the period's error budget has been used. The budget used is the number of bad requests divided by the number the objective allows.

    Calculation:

    • Allowed bad requests: (1 − objective) × total requests.
    • Budget used: bad requests / allowed bad requests.

    Making it production-ready:

    1. Read counts from the metrics system rather than raw logs, for completeness and speed.
    2. Use the objective's real window, such as 30 rolling days, not calendar months, unless that is the agreement.
    3. Show the days that consumed most of the budget, linked to incidents.
    4. Publish the report automatically, so teams see budget status without asking.

    A budget above 100% means the objective was missed for the period, which should trigger the error budget policy.

    Example

    Eight requests over two days, with a 70% objective and a 300 ms threshold.

    from collections import defaultdict
    
    requests = [
        ("2026-03-01", 200, 120), ("2026-03-01", 200, 180), ("2026-03-01", 500, 90), ("2026-03-01", 200, 250),
        ("2026-03-02", 200, 140), ("2026-03-02", 200, 450), ("2026-03-02", 200, 160), ("2026-03-02", 200, 130),
    ]
    objective, threshold_ms = 0.70, 300
    
    daily = defaultdict(list)
    for day, status, ms in requests:
        daily[day].append(status < 500 and ms <= threshold_ms)
    
    bad = total = 0
    for day, results in sorted(daily.items()):
        good = sum(results)
        bad += len(results) - good
        total += len(results)
        print(f"{day}: {good}/{len(results)} good ({good / len(results):.0%})")
    print(f"budget used: {bad / ((1 - objective) * total):.0%}")
    # Output:
    # 2026-03-01: 3/4 good (75%)
    # 2026-03-02: 3/4 good (75%)
    # budget used: 83%
  16. Q76.A service burned 40% of its monthly error budget in one day. What do you do?

    Treat it as an incident if the burn is still happening, then find what consumed the budget and decide, using the error budget policy, how the team's priorities change for the rest of the period.

    1. Check whether it is ongoing: if the burn rate is still high, mitigate first, with a rollback, failover or load shedding.
    2. Find the cause: look at when the bad events happened, which endpoints, regions and versions they affected, and what changed.
    3. Confirm the measurement: a broken probe, a misconfigured indicator or a bot flood can burn budget without real user impact; fix the measurement if so, but do not erase genuine failures.
    4. Apply the policy: with 60% left for the rest of the month, the policy may require slowing risky releases or prioritizing the fix.
    5. Review the incident and assign actions, particularly for detection, if the burn was noticed late.
    6. Communicate budget status to product owners, so changes to plans are expected rather than surprising.

    Interview tip: saying you would check that the indicator itself was accurate, before changing team plans, shows maturity without dodging accountability.

    Example

    A 99.9% objective over 30 days allows about 43 minutes of full outage.

    1. A bad deployment caused a 15% error rate for 2 hours: equivalent to 0.15 × 120 = 18 minutes of full outage, about 42% of the budget.
    2. The deployment had already been rolled back, so the burn had stopped.
    3. The review found that automated rollback was disabled for that service.
    4. The policy required re-enabling automated rollback before the next release, and the team paused non-essential changes for a week.
  17. Q77.Latency rises every day at the same hour. How do you investigate?

    A daily pattern points to something scheduled or to a daily traffic peak. Line up the latency increase with everything that happens at that time, in your service and in its dependencies.

    Candidates to check:

    1. Scheduled jobs: backups, batch exports, report generation, cache rebuilds or log rotation on the same hosts or databases.
    2. Traffic peaks: the daily busiest hour, perhaps combined with too little capacity or slow autoscaling.
    3. Dependency schedules: another team's batch job hitting a shared database or network link.
    4. Cache expiry: entries with fixed lifetimes created at the same time each day, expiring together.
    5. Maintenance tasks: database statistics updates, compaction or index rebuilding.
    6. Certificate or credential refreshes that briefly slow connections.

    How to confirm:

    • Compare resource use on databases and hosts at that hour with other hours.
    • Check job schedulers and scheduled-task configurations across teams for that time.
    • Temporarily move a suspected job and see whether the spike moves with it.

    Typical fixes: reschedule or throttle the batch job, isolate it on replicas, add jitter to expiry times or scale ahead of the daily peak.

  18. Q78.A deployment caused errors in only one region. What do you check?

    If the same version works elsewhere, the difference is in that region's environment, data or traffic. Mitigate by rolling back or shifting traffic away, then compare the region with healthy ones.

    1. Mitigate: roll back the region, or move its traffic to healthy regions if they have capacity.
    2. Configuration: region-specific settings, feature flags or secrets that differ, including ones that were never updated.
    3. Dependencies: a regional database, cache or third-party endpoint that behaves differently, or is on a different version.
    4. Infrastructure: different instance types, network rules, quotas or a capacity shortage in that region.
    5. Data: region-specific data, such as a locale, currency or character set, that triggers a code path not exercised elsewhere.
    6. Traffic: different client versions or request mixes in that region.
    7. Rollout order: if that region received the change first, it may simply be the first to show a general bug.

    Prevent it next time: keep regional configuration in version control with validation, and roll out region by region with health checks between steps, so a regional problem stays regional.

  19. Q79.After a failover, the backup region collapsed under the load. What went wrong, and how do you prevent it?

    The backup region was not truly ready to carry the primary region's traffic: capacity, caches, quotas or dependencies were sized for its normal load, not for the combined load after failover.

    flowchart TD
      A["Primary region fails"] --> B["All traffic shifts to backup region"]
      B --> C["Caches are cold, autoscaling is slow"]
      C --> D["Databases and dependencies overload"]
      D --> E["Errors and retries add more load"]
      E --> F["Backup region fails too"]

    Common causes:

    • Capacity sized for normal regional traffic, relying on autoscaling that is too slow or limited by quotas.
    • Cold caches sending all reads to databases.
    • Connection limits on databases and third-party services.
    • Retries from clients during the outage adding a surge on top.

    Prevention:

    1. Provision for failover load, or keep enough warm capacity plus fast scaling with confirmed quotas.
    2. Shift traffic gradually when possible, rather than all at once.
    3. Warm caches in the backup region by serving some real traffic there all the time.
    4. Shed load during failover, prioritizing critical journeys.
    5. Test full failover under realistic load regularly, not just the switching mechanism.
  20. Q80.An outage in a shared dependency took down many services. How do you reduce the blast radius in the future?

    When one dependency can take down everything, the architecture has a single point of failure in practice, even if each service looks independent. Reduce how much each service depends on it and how many services one failure can reach.

    Ways to reduce blast radius:

    1. Make it a soft dependency where possible: caches, defaults or queued work, so services keep running in a reduced mode.
    2. Partition the dependency: separate instances per domain or per tier, so critical services do not share a cluster with batch workloads.
    3. Cell-based architecture: split users across independent copies of the stack, so one failing cell affects only its share of users.
    4. Bulkheads and timeouts in every client, so a slow dependency cannot exhaust callers' resources.
    5. Staged changes to the dependency, rolled out to one partition at a time.
    6. Map dependencies: keep an up-to-date graph showing which services and journeys rely on each shared component.
    flowchart LR
      A["Users"] --> B["Cell 1: full stack, 25% of users"]
      A --> C["Cell 2: full stack, 25% of users"]
      A --> D["Cell 3: full stack, 25% of users"]
      A --> E["Cell 4: full stack, 25% of users"]

    With cells, a failure in one shared component affects a quarter of users rather than everyone.