Multi-Cloud and Hybrid Cloud Architecture Questions
Designing systems that span multiple cloud providers or bridge cloud and on-premises. Covers cloud-agnostic abstraction, workload placement across providers or environments, cross-cloud networking and identity federation, data gravity, infrastructure-as-code and centralized observability that span providers, and the operational cost of avoiding vendor lock-in versus the risk of accepting it. Also covers keeping a system correct once it spans providers: leader election, distributed transactions, rate limiting, and service discovery across cloud or cluster boundaries. Resilience patterns here are scoped to crossing a provider or on-prem/cloud boundary (for example failover from one provider to another, or from on-prem to cloud). Resilience across regions of a single provider, with no second provider or on-prem leg involved, is a different topic (multi-region architecture) and is out of scope here.
Design a GitOps-based change management model for hybrid network configurations: define IaC for network resources, pull-request based approvals, automated policy checks (OPA/terraform-compliance), drift detection and remediation, and integration with cloud provider config APIs and on-prem device management. Outline how emergency changes are handled and audited.
Sample Answer
Direct answer
Treat every hybrid network change, cloud-side and on-prem-side, as a pull request against a Git repository
of declared intent, gated by automated policy checks before a human ever reviews it, applied only after
merge, and continuously reconciled against live state afterward so drift is caught within minutes rather than
at the next audit.
Structured elaboration
IaC for network resources. Represent every network object, VPCs/VNets, route tables, firewall rules,
on-prem switch and firewall configuration where the vendor supports a declarative interface, as code in one
repository, so a router ACL (access control list, a set of allow/deny rules) change and a cloud security
group change go through the identical review process instead of two different ones with two different bars
for scrutiny.
Pull-request based approvals. Every change is a PR (pull request) with a diff a reviewer can actually
read: "widen this security group from port 443 to all ports" is visible in the diff itself, not buried in a
console click. Require at least one independent approval before merge, and for genuinely high-risk changes
(anything touching the on-prem-to-cloud boundary itself) require two.
Automated policy checks. Run OPA (Open Policy Agent, a general-purpose policy engine) or
terraform-compliance against every PR before a human reviews it, checking rules like "no security group may
allow inbound 0.0.0.0/0 on a management port" automatically, so reviewers spend their attention on intent and
architecture, not on manually re-deriving policy violations a machine can catch in seconds.
Drift detection and remediation. Run a scheduled job (hourly or more frequent for the boundary itself)
that compares live configuration against the last-applied Git state. A detected drift either auto-reverts
for low-risk, easily reversible changes, or pages a human for anything touching routing or the security
boundary, since auto-reverting a security-relevant emergency change someone made by hand could itself cause
an outage.
Integration with provider config APIs and on-prem device management. The same pipeline that calls cloud
provider APIs (via Terraform providers, for example) also needs a path to on-prem device management,
whether that is a vendor's own API, Ansible against network devices, or a network automation platform;
without that second path, "GitOps for hybrid" quietly becomes "GitOps for the cloud half only," and the
on-prem half stays manually managed and invisible to drift detection.
Emergency changes. Define a break-glass path explicitly: a named, time-boxed exception that allows a
direct change outside the normal PR flow during an active incident, with a mandatory retroactive PR within a
fixed window (for example, one business day) that brings the emergency change back into Git as the source of
truth, and an audit log entry generated automatically the moment break-glass access is used, not manually
written after the fact.
flowchart TD
Dev[Engineer opens PR] --> Policy[OPA / terraform-compliance checks]
Policy -- fail --> Dev
Policy -- pass --> Review[Peer review + approval]
Review --> Merge[Merge to main]
Merge --> Apply[Pipeline applies IaC]
Apply --> CloudAPI[Cloud provider config APIs]
Apply --> OnPremMgmt[On-prem device management system]
Drift[Drift detector, scheduled] --> Compare{Live state matches Git?}
CloudAPI --> Drift
OnPremMgmt --> Drift
Compare -- no --> Remediate[Auto-revert or alert]
Compare -- yes --> Idle[No action]
Worked example
An engineer opens a PR widening an on-prem firewall rule to let a new cloud burst pool reach an internal
API. The OPA policy check fails immediately because the rule as written allows the port from any source, not
just the burst pool's specific CIDR; the engineer narrows the rule, the check passes, a peer reviews and
approves, and the pipeline applies it through the on-prem device management API. Two weeks later, an
on-call engineer makes an emergency, direct change to that same rule at 2 a.m. to resolve an active incident,
using the documented break-glass path. The drift detector flags the divergence from Git within the hour; the
audit log already shows who used break-glass and why, and the engineer files the retroactive PR the next
morning, restoring Git as the single source of truth for that rule.
Trade-offs and pitfalls
The most common failure is building this pipeline for the cloud side only, because cloud provider APIs are
easy to automate against and on-prem device management often is not, which quietly defeats the point:
the boundary itself, the thing this question is actually about, ends up as the one part still managed by
hand. A second pitfall is auto-reverting every detected drift unconditionally: a well-intentioned emergency
fix made outside the pipeline during a live incident can get silently undone by the drift remediator,
turning one incident into two. Scope auto-revert to genuinely low-risk changes and alert-only for anything
near the security boundary.
List essential network monitoring metrics and telemetry that should be collected for hybrid and multi-cloud networks. Cover performance metrics (latency, jitter, packet loss), capacity (utilization), health (BGP session state), security signals, and which data sources (NetFlow/IPFIX, VPC flow logs, SNMP, cloud metrics) to use.
Sample Answer
Direct answer
Cover four categories: performance (is traffic getting through cleanly), capacity (how much headroom is
left), health (is the routing itself stable), and security (is anything unexpected happening on the link),
each fed by a specific, named data source rather than a generic "monitor the network" statement.
Structured elaboration
Performance: latency, jitter, packet loss. Latency (round-trip time) tells you how long a hop takes;
jitter (variation in latency between packets) matters most for real-time traffic like voice or video, where
a stable but slightly higher latency is often better than a lower but wildly variable one; packet loss
directly degrades TCP (Transmission Control Protocol) throughput and application-level retries. Measure all
three continuously, not just when something is already reported broken.
Capacity: utilization. Track bandwidth utilization on every hybrid link (VPN tunnel, dedicated circuit)
against its provisioned capacity, since a link running near saturation shows degraded performance well
before it shows outright failure, and utilization trending upward over weeks is the actionable early warning
a point-in-time check misses.
Health: BGP session state. Track whether each BGP (Border Gateway Protocol) session is Established (the
healthy state) versus flapping between states, since a flapping session, one that repeatedly drops and
reconnects, degrades routing stability even while technically reporting as "up" most of the time, and is
often an early symptom of a link or hardware problem that has not yet caused a full outage.
Security signals. Watch for anomalous traffic patterns crossing the hybrid boundary: an unexpected spike
in traffic volume or a connection attempt to a port that should never be reachable across that boundary,
both often visible in flow-level telemetry before a dedicated security tool would flag them.
Data sources. NetFlow and IPFIX (IP Flow Information Export, both flow-record protocols that summarize
who talked to whom, how much, and for how long) work on-prem and on any router that supports them; VPC flow
logs are the cloud-native equivalent for AWS traffic (Azure and GCP have their own named equivalents); SNMP
(Simple Network Management Protocol) polls device-level health metrics like interface utilization and error
counters from on-prem hardware; and each cloud's own native metrics service (CloudWatch, Azure Monitor,
Cloud Monitoring) reports the cloud side of link and gateway health directly.
Worked example
A hybrid link between an on-prem datacenter and AWS shows rising latency over three days, from a baseline of
15ms to 40ms, visible in CloudWatch's tunnel metrics on the AWS side and matched against NetFlow data
on-prem, before any user-facing symptom appears. The BGP session state stays Established throughout, so a
health check alone would have reported everything as fine. The utilization metric on the same link shows it
crossing 70 percent of provisioned capacity over the same three days, correlating with the latency rise and
correctly identifying the actual cause: the link is approaching saturation, not failing outright.
Trade-offs and pitfalls
The main mistake on an easy version of this question is naming only one or two categories, usually
performance metrics, and missing capacity or health entirely, both of which catch problems before they
become performance problems. A second, subtler mistake is naming a metric without a data source: knowing you
should track BGP session state is different from knowing SNMP or the cloud provider's own routing metrics
are where that data actually comes from.
You manage connectivity between on-premises data centers, Cloud A, and Cloud B. One cloud region becomes unreachable. Walk me through how you would detect the failure, withdraw or reroute traffic, and avoid routing loops or blackholes during failover.
Sample Answer
Direct answer
Detect the failure through BGP (Border Gateway Protocol, the protocol that dynamically advertises which
networks are reachable through which path) session loss backed by a fast, subsecond mechanism rather than
waiting on BGP's own timers alone, withdraw only the specific routes affected rather than the whole path, and prevent
loops and blackholes by making sure every remaining path has a real, verified route to the destination
before traffic is sent there, not just an entry that looks plausible in a routing table.
Structured elaboration
Detection. BGP's own failure detection relies on hold timers, commonly 180 seconds with 60-second
keepalives in practice (RFC 4271, the IETF standards document that specifies BGP itself, only requires a Hold
Time of at least three seconds, or zero to disable it, and recommends a Keepalive interval of roughly one
third of the Hold Time without mandating a specific pair of numbers; most vendor implementations, including
what AWS and most enterprise routers ship, default to the 180/60 pair), which is far too slow for a
production failover. Layer BFD (Bidirectional Forwarding Detection, a lightweight protocol that detects a
dead peer in tens to hundreds of milliseconds) underneath BGP so the routing protocol reacts to a real link
or peer failure almost immediately instead of waiting three minutes for its own timer to expire.
Withdrawal and rerouting. When Cloud A's region becomes unreachable, the router(s) that had a BGP
session to it should withdraw exactly the routes learned from that session, nothing more, and the
routing table should already hold a valid alternate path (learned from Cloud B or from a second on-prem
egress) with a less-preferred local preference (a BGP attribute a router uses to rank its own preferred exit path
when more than one exists, independent of MED) or higher MED (multi-exit discriminator, a metric BGP uses to
choose between multiple paths to the same destination) so failover is a preference switch, not a new route
being computed from scratch under pressure.
Avoiding routing loops. A loop forms when two routers each believe the other is the best path to a
destination that neither can actually reach. Route summarization, common in hybrid designs to keep routing
tables small, makes this worse if applied carelessly: a summarized route can keep advertising reachability
for a destination whose specific subnet is actually unreachable behind the failed link, because the summary
itself did not change even though what it covers did. Avoid summarizing across the exact boundary that can
fail independently, or disaggregate (advertise more specific routes) automatically the moment that boundary
goes down.
Avoiding blackholes. A blackhole is a route that looks valid in the table but leads nowhere, exactly the
failure mode careless summarization causes. Guard against it with a "route exists in the table" check backed
by a real reachability check (a health check across the alternate path, not just BGP session state), since a
neighboring router can still hold a stale route it has not yet withdrawn even after the actual path behind
it has failed.
Worked example
On-prem connects to both Cloud A and Cloud B over separate BGP sessions, each summarized as a single /16
advertisement covering several /24 subnets behind it. Cloud A's region goes dark. BFD detects the dead
peer within 300 milliseconds and tears down the BGP session to Cloud A immediately, rather than waiting the
approximately 180 seconds a hold-timer-only design would take. Because the /16 summary was configured to
automatically disaggregate on link failure, on-prem correctly stops advertising reachability for the
specific /24 subnets that were actually behind Cloud A's failed link, rather than continuing to advertise
the whole summary and blackholing traffic destined for those subnets into a dead path. Traffic destined for
Cloud A shifts to Cloud B's session within roughly half a second of the actual failure, verified by a
synthetic health check that confirms the alternate path is not just present in the table but actually
reachable end to end.
Trade-offs and pitfalls
The dominant real-world mistake is relying on BGP's default timers alone and discovering during an actual
outage that failover takes minutes, not seconds, because BFD was never configured. A second, subtler mistake
is summarizing routes for table-size hygiene without also planning for what happens to that summary when
part of what it covers fails, which is exactly how a summary route turns into a blackhole instead of a
convenience. State both explicitly in an interview: fast failure detection and summarization-aware failover
behavior are the two things that separate a design that works on a whiteboard from one that works during a
real regional outage.
Design DNS resolution for a hybrid environment where on-prem services must resolve cloud-private hostnames and cloud workloads need to reach on-prem servers by internal names. Cover split-horizon DNS, conditional forwarding, resolver endpoints (e.g., Route53 Resolver/Azure Private DNS), caching, TTL strategy, and security considerations.
Sample Answer
Direct answer
Run split-horizon DNS (the same domain name resolving differently depending on where the query originates) backed by conditional forwarding in both directions: on-prem resolvers forward queries for cloud-private zones to a cloud-provided resolver endpoint, Route 53 Resolver inbound and outbound endpoints on AWS, Azure DNS Private Resolver on Azure, and the cloud side forwards queries for on-prem zones back the other way over the same private connectivity (Direct Connect, AWS's dedicated private network link to an on-prem site; ExpressRoute, Azure's equivalent; or a VPN). Neither side ever needs to expose an authoritative DNS server for the other's private zone to the public internet.
Structured elaboration
Split-horizon and conditional forwarding
A host on-prem asking for an internal cloud name should get the cloud-private answer, and in practice most designs keep the zones cleanly separated (on-prem.corp versus cloud.corp) specifically to avoid the ambiguity a true split-horizon setup otherwise creates. Conditional forwarding rules are the mechanism: for a given domain, do not use the default resolver, forward to this specific IP instead.
Resolver endpoints, by provider
| Provider | Inbound (accepts queries from the other side) | Outbound (sends queries to the other side) |
|---|---|---|
| AWS | Route 53 Resolver inbound endpoint: an IP in the VPC (Virtual Private Cloud, an isolated private network inside AWS) that on-prem resolvers forward to | Route 53 Resolver outbound endpoint, paired with a forwarding rule per domain |
| Azure | Azure DNS Private Resolver inbound endpoint, an IP inside the VNet (Virtual Network, Azure's equivalent of a VPC) | Azure DNS Private Resolver outbound endpoint plus a DNS forwarding ruleset, up to 1,000 rules |
| GCP | Cloud DNS server policy, inbound forwarding | Cloud DNS forwarding zone, per on-prem domain |
Both AWS and Azure require the private connectivity to already exist before the resolver endpoints are useful, since the endpoints only work over that private path. Azure's DNS Private Resolver is worth naming precisely because it replaced an older, unmanaged pattern of running dedicated DNS-forwarder virtual machines in a VNet; the managed service removes the patching and high-availability burden that pattern carried.
Caching and TTL strategy
Records tied to something that might fail over (a database endpoint, a load balancer VIP) should carry a low TTL (Time-To-Live), commonly in the 30 to 60 second range, so a failover actually takes effect quickly instead of being masked by stale caches on every resolver in the path; the cost is more query volume against the resolver endpoints, which is exactly what an endpoint's query-per-second limit exists to protect (Azure documents 10,000 queries per second per Private Resolver endpoint, generous for this pattern but not infinite). Records that never move (a shared services hostname, an internal package mirror) can carry a much longer TTL, an hour or more, to cut query volume with no downside.
Security considerations
Restrict who can query each resolver endpoint to the specific CIDR (Classless Inter-Domain Routing) ranges on the other side of the connection, not anyone who can route to it, since a resolver endpoint that quietly accepts queries from anywhere on the internal network is a reconnaissance tool for anyone who compromises one host. Enable query logging on both the AWS and Azure resolver services; it is one of the highest-value, lowest-cost forensic signals available, because DNS queries reveal what a compromised host is trying to reach before it ever reaches it. Finally, watch DNS suffix search lists on hybrid hosts: a host configured to try both an on-prem suffix and a cloud suffix can end up querying the wrong side's resolver for an unqualified name, a subtle and common source of resolving to the wrong environment.
Worked example
A host on-prem looks up orders-db.cloud.corp. Its local resolver has a conditional forwarding rule for cloud.corp pointing at 10.50.0.2 and 10.50.0.3, the Route 53 Resolver inbound endpoint IPs. That query crosses the Direct Connect link, Route 53 Resolver answers from the private hosted zone, and the response's 30-second TTL means that if orders-db fails over to a new IP during a database failover, every on-prem resolver has stopped trusting the old answer within 30 seconds, well inside most applications' retry budget.
Trade-offs and pitfalls
DNSSEC (DNS Security Extensions) and split-horizon are in genuine tension: a signed zone is supposed to give one verifiable answer, and serving a different, unsigned or differently-signed answer depending on where the query originates undermines that guarantee, so most hybrid designs either skip DNSSEC on the internal split-horizon zones entirely or scope it carefully to zones that are not view-dependent. The most common operational pitfall is not a DNS problem at all: the resolver endpoints depend on the private connectivity being up, so a Direct Connect or ExpressRoute outage does not just break application traffic, it breaks the very name resolution needed to diagnose the outage, which is why a low-TTL secondary path, even a slower VPN-based one, for the resolver endpoints specifically is worth the modest extra cost.
Write a Python script (you may outline code) that periodically polls three cloud provider health endpoints for your application-focused health checks and marks a region unhealthy if it reports 5 consecutive failures. Describe idempotency, retries, and alerting behavior. Assume you have provider APIs that return 200 on healthy.
Sample Answer
Approach
Poll each of the three cloud provider health endpoints on a fixed interval, track a per-region consecutive-failure counter, and flip a region to unhealthy the moment that counter hits 5, firing exactly one alert for that outage rather than one alert per failed poll after the fact. The two things that make this production-safe rather than a toy loop are: retries within a single poll use exponential backoff so a one-second network blip doesn't get counted as a real failure, and the alert itself is idempotent, tracked per outage, so a region that stays down for an hour pages once, not once every 30 seconds.
import time
import logging
from dataclasses import dataclass
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("region-health")
FAILURE_THRESHOLD = 5
MAX_RETRY_ATTEMPTS = 3
RETRY_BACKOFF_BASE_SECONDS = 1
@dataclass
class RegionState:
name: str
consecutive_failures: int = 0
healthy: bool = True
alerted_for_current_outage: bool = False # idempotency: one alert per outage, not per poll
def check_once(fetch_status_fn, region: str, cycle: int) -> bool:
"""Single health check with bounded retries and exponential backoff.
fetch_status_fn(region, cycle) -> int (HTTP status code), raises on transport error.
A real regional outage does not clear itself between two 1-second retries, so all
retries within one poll are scored against the SAME cycle's scripted status; only
the outer polling loop (a fresh cycle, ~30s later against real endpoints) can observe
a recovered region. Returns True if 200 was seen within MAX_RETRY_ATTEMPTS tries.
"""
for attempt in range(1, MAX_RETRY_ATTEMPTS + 1):
try:
status = fetch_status_fn(region, cycle)
if status == 200:
return True
log.warning("region=%s cycle=%d attempt=%d non-200 status=%s", region, cycle, attempt, status)
except Exception as exc:
log.warning("region=%s cycle=%d attempt=%d transport error=%s", region, cycle, attempt, exc)
if attempt < MAX_RETRY_ATTEMPTS:
time.sleep(RETRY_BACKOFF_BASE_SECONDS * (2 ** (attempt - 1)))
return False
def send_alert(region: str, streak: int, state: RegionState):
"""Idempotent: fires exactly once per outage, on the poll that CROSSES the threshold,
not again on every subsequent still-down poll."""
if state.alerted_for_current_outage:
return
log.error("ALERT region=%s marked UNHEALTHY after %d consecutive failures", region, streak)
state.alerted_for_current_outage = True
def send_recovery(region: str):
log.info("RECOVERY region=%s back to HEALTHY", region)
def poll_regions(fetch_status_fn, region_names, cycles):
"""Drives `cycles` poll rounds across all regions and returns final states."""
states = {name: RegionState(name=name) for name in region_names}
for cycle in range(1, cycles + 1):
for name in region_names:
state = states[name]
ok = check_once(fetch_status_fn, name, cycle)
if ok:
if not state.healthy:
send_recovery(name)
state.consecutive_failures = 0
state.healthy = True
state.alerted_for_current_outage = False
else:
state.consecutive_failures += 1
if state.consecutive_failures >= FAILURE_THRESHOLD:
state.healthy = False
send_alert(name, state.consecutive_failures, state)
log.info(
"region=%s cycle=%d consecutive_failures=%d healthy=%s",
name, cycle, state.consecutive_failures, state.healthy,
)
return states
# ---- deterministic simulation of three provider endpoints, keyed by CYCLE not call count ----
def make_fake_endpoint(down_during_cycles: set):
def fetch(region, cycle):
return 500 if cycle in down_during_cycles else 200
return fetch
def run_demo():
# Provider B is down for cycles 3 through 7 inclusive (5 straight bad polls, exactly
# enough to cross the 5-consecutive-failure threshold), then recovers at cycle 8.
fetchers = {
"provider-a": make_fake_endpoint(down_during_cycles=set()),
"provider-b": make_fake_endpoint(down_during_cycles=set(range(3, 8))),
"provider-c": make_fake_endpoint(down_during_cycles=set()),
}
def dispatch(region, cycle):
return fetchers[region](region, cycle)
final_states = poll_regions(dispatch, ["provider-a", "provider-b", "provider-c"], cycles=10)
print("\n--- FINAL STATE AFTER 10 POLL CYCLES ---")
for name, state in final_states.items():
print(f"{name}: healthy={state.healthy} consecutive_failures={state.consecutive_failures}")
if __name__ == "__main__":
run_demo()
Key points
- Idempotency:
alerted_for_current_outageon the region's state is the whole idempotency mechanism. It resets toFalsethe moment the region recovers, so the next distinct outage correctly pages again; without this flag the alert function would fire on every poll after the threshold, not just the one that crosses it. - Retries: each poll gets up to 3 attempts with exponential backoff (1s, 2s) before being counted as one failed cycle. This absorbs transient network blips without needing a longer, coarser polling interval that would slow down real outage detection.
- Alerting behavior: the alert function is called only from the branch where
consecutive_failures >= FAILURE_THRESHOLD, and it self-guards against re-firing via the state flag, so the alerting policy (once per outage) is enforced in one place rather than scattered across the polling loop.
Output
Running the script above produces the following (log timestamps will differ run to run, but the sequence of events, cycle numbers, and final state are deterministic because the simulated endpoint's response is a pure function of the cycle number, not wall-clock time):
2026-09-23 17:33:51,111 WARNING region=provider-b cycle=3 attempt=1 non-200 status=500
2026-09-23 17:33:52,119 WARNING region=provider-b cycle=3 attempt=2 non-200 status=500
2026-09-23 17:33:54,122 WARNING region=provider-b cycle=3 attempt=3 non-200 status=500
2026-09-23 17:33:54,122 INFO region=provider-b cycle=3 consecutive_failures=1 healthy=True
2026-09-23 17:33:54,122 WARNING region=provider-b cycle=4 attempt=1 non-200 status=500
2026-09-23 17:33:55,131 WARNING region=provider-b cycle=4 attempt=2 non-200 status=500
2026-09-23 17:33:57,141 WARNING region=provider-b cycle=4 attempt=3 non-200 status=500
2026-09-23 17:33:57,141 INFO region=provider-b cycle=4 consecutive_failures=2 healthy=True
2026-09-23 17:33:57,141 WARNING region=provider-b cycle=5 attempt=1 non-200 status=500
2026-09-23 17:33:58,149 WARNING region=provider-b cycle=5 attempt=2 non-200 status=500
2026-09-23 17:34:00,164 WARNING region=provider-b cycle=5 attempt=3 non-200 status=500
2026-09-23 17:34:00,164 INFO region=provider-b cycle=5 consecutive_failures=3 healthy=True
2026-09-23 17:34:00,165 WARNING region=provider-b cycle=6 attempt=1 non-200 status=500
2026-09-23 17:34:01,175 WARNING region=provider-b cycle=6 attempt=2 non-200 status=500
2026-09-23 17:34:03,185 WARNING region=provider-b cycle=6 attempt=3 non-200 status=500
2026-09-23 17:34:03,185 INFO region=provider-b cycle=6 consecutive_failures=4 healthy=True
2026-09-23 17:34:03,185 WARNING region=provider-b cycle=7 attempt=1 non-200 status=500
2026-09-23 17:34:04,195 WARNING region=provider-b cycle=7 attempt=2 non-200 status=500
2026-09-23 17:34:06,205 WARNING region=provider-b cycle=7 attempt=3 non-200 status=500
2026-09-23 17:34:06,205 ERROR ALERT region=provider-b marked UNHEALTHY after 5 consecutive failures
2026-09-23 17:34:06,205 INFO region=provider-b cycle=7 consecutive_failures=5 healthy=False
2026-09-23 17:34:06,205 INFO RECOVERY region=provider-b back to HEALTHY
--- FINAL STATE AFTER 10 POLL CYCLES ---
provider-a: healthy=True consecutive_failures=0
provider-b: healthy=True consecutive_failures=0
provider-c: healthy=True consecutive_failures=0
Provider B accumulates failures through cycles 3 to 7, crosses the threshold of 5 exactly at cycle 7 (firing exactly one ALERT log line, not five), and recovers at cycle 8 (firing exactly one RECOVERY log line), which is why the final summary after 10 cycles shows all three providers healthy with zero consecutive failures: the run completes after the recovery.
Complexity and edge cases
Complexity: O(1) work per region per poll cycle (a fixed number of retry attempts and a counter update), so the whole loop is O(regions x cycles), independent of history length; no unbounded state accumulates per region.
Edge cases:
- A region flapping right at the threshold (4 failures, 1 success, 4 failures) never crosses 5 consecutive and correctly never alerts, because the counter resets to zero on any single success. This is intentional: the requirement is 5 consecutive failures, not 5 failures within some window.
- A transport exception (connection refused, timeout) is treated identically to a non-200 status code; the retry and threshold logic doesn't need to distinguish "the endpoint answered with an error" from "the endpoint didn't answer at all," since both mean the region can't currently be confirmed healthy.
- If the alerting channel itself is down when
send_alertfires, this implementation only logs; a production version would need the alert dispatch itself to retry or fall back to a secondary channel, which is a real gap worth calling out explicitly rather than assuming logging is sufficient for a real on-call page.
Unlock Full Question Bank
Get access to all Multi-Cloud and Hybrid Cloud Architecture interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.