Safe Deployment and Rollback Strategies Questions
Releasing changes to production safely and incrementally, and recovering when they fail: blue-green, canary, and rolling deployments, feature flags, dark launches, traffic shifting, and progressive rollout, together with rollback strategies, safe-deploy practices, blast-radius containment, automated recovery, and safe forward/backward migration. Covers deployment orchestration across cloud platforms, staged exposure of new behavior to users, assessing deployment risk, designing reversible releases, and restoring a known-good state quickly. Focuses on how a release reaches production and how it is unwound on failure, distinct from broader incident command, which lives in Enterprise Operations & Incident Management.
Propose a rollout strategy for a stateful service, such as a Redis cluster, that needs a version upgrade without data loss: coordination, backup, and failover.
Sample Answer
Direct answer
Upgrading a stateful service like a Redis cluster without data loss means treating the upgrade as a carefully sequenced, node-by-node operation with a verified backup as the safety net, never a wholesale replace-everything-at-once approach, since a stateful cluster's whole value is the data it's currently holding, which a naive rollout could easily lose.
Structured elaboration
- Backup first, always: before touching anything, a verified (not just taken, but confirmed restorable) backup or snapshot of the current cluster state, so there's a fallback that doesn't depend on the upgrade or rollback mechanism working correctly.
- Understand the cluster's replication/failover topology: for Redis specifically, a cluster typically has primary and replica nodes; the upgrade sequence should upgrade REPLICAS first (since losing a replica temporarily doesn't lose data or availability, the primary is still serving), then trigger a controlled failover to an already-upgraded replica (promoting it to primary), THEN upgrade the now-demoted former primary as the last node.
- Coordination during the failover step specifically: a controlled failover (not a crash-triggered one) briefly pauses writes or redirects them, depending on the client library's failover handling, so this moment needs its own testing and a defined acceptable interruption window, since it's the one point in the sequence where a genuine, if brief, disruption is likely.
- Verify data consistency after each node's upgrade, not just at the end: confirming a freshly-upgraded replica has caught up and is correctly replicating before proceeding to the next step, rather than assuming replication "worked" without checking.
- Rollback path if something goes wrong mid-sequence: since nodes are upgraded one at a time, a problem discovered on an upgraded replica can be handled by NOT promoting it (avoiding it becoming primary) and instead restoring it from the still-healthy cluster's current replication state, or from the verified backup if the cluster itself is compromised, without needing to touch the nodes not yet upgraded.
Worked example
A 3-node Redis cluster (1 primary, 2 replicas): upgrade replica A first, verify it's caught up and serving correctly, upgrade replica B, verify the same, then trigger a controlled failover promoting one of the now-upgraded replicas to primary, verify the new primary is stable, and finally upgrade the former primary (now a replica). Throughout, a pre-upgrade backup exists as the ultimate fallback if something goes wrong that node-by-node rollback alone can't cleanly fix.
Trade-offs and pitfalls
This sequential, verify-at-each-step approach is meaningfully slower than a bulk replace-all-at-once upgrade, but the bulk approach risks momentarily having no healthy, upgraded node capable of correctly serving if something goes wrong mid-upgrade across multiple nodes simultaneously, a much worse failure mode for a stateful cluster than for stateless compute. The common mistake is treating a stateful cluster's upgrade like a stateless rolling update (just replace instances gradually) without accounting for the coordination REPLICATION and FAILOVER require, which stateless services simply don't have.
Services A and B were updated together. A's update is backward-compatible, but B's new version introduced incompatible writes, and you must roll back B while keeping A on its new version. How do you handle in-flight and already-persisted inconsistent state so the system reaches eventual consistency?
Sample Answer
Direct answer
When B must roll back but A stays on its new (backward-compatible) version, the core problem is that B's incompatible writes may have already happened before you detect the issue, so the fix isn't just "redeploy old B," it's identifying and correcting whatever inconsistent state those writes left behind, while B's rollback itself is straightforward since A's compatibility means A doesn't need to change at all.
Structured elaboration
- Stop further damage: roll back B's code immediately (this part is simple exactly because A is backward-compatible with B's old version, no coordination needed there), which stops NEW incompatible writes from happening, but doesn't undo ones that already occurred.
- Identify what's actually inconsistent: determine which writes B made in its brief new-version window were the problematic, incompatible ones, versus which writes (even during that window) were fine; this typically requires either an audit log of what changed, or a way to distinguish "written by new B" from "written by old B" (a version marker on the data itself, if you have one, or inferring from timestamps against the deploy window).
- In-flight requests specifically: any request that started against new-B logic but hasn't yet completed when the rollback happens needs explicit handling, either let it finish against the code it started with (avoiding a mid-request logic switch) and then reconcile its result afterward if needed, or, if that's not safe, actively cancel/retry it against the now-rolled-back old B.
- Reconciliation toward eventual consistency: for the identified inconsistent writes, either a compensating action (a corrective write that brings the data back to what old-B's logic would have produced) or, if the incompatible writes are numerous and hard to individually correct, a broader reconciliation job that recomputes affected records from source data, run once B is confirmed stable on the old version again.
- Idempotency throughout: both the rollback itself and any compensating/reconciliation actions need to be safely re-runnable, since this kind of recovery work is exactly the scenario where a retry (from a nervous on-call engineer, or from an automated retry mechanism) is likely.
Worked example
Service B's new version wrote records in a new, incompatible format for roughly 8 minutes before the regression was caught and B rolled back. An audit log (or a version-tagged field on the written records) identifies exactly which records fall in that window; a reconciliation job re-derives the correct, old-format value for each of those specific records from upstream source data, run idempotently (safe to re-trigger if it's interrupted partway through) so a retry doesn't double-apply the correction.
Trade-offs and pitfalls
The scope of this problem is directly proportional to how LONG the incompatible version was live before detection, which is a strong argument for the fast, automated rollback-trigger discipline covered elsewhere in this topic; a regression caught in 30 seconds via automated detection leaves a much smaller reconciliation problem than one caught 30 minutes later via a human noticing something looked off. The common mistake is treating the code rollback as the end of the incident, when the DATA reconciliation is often the harder, longer-running part of actually resolving it.
You're performing a blue-green cutover behind a global CDN. How do you switch traffic without serving stale content or poisoning the cache, and what would you check before and after the switch?
Sample Answer
Direct answer
Switching traffic during a blue-green cutover behind a global CDN needs to account for the CDN's OWN cache, which is a separate layer from the origin switch itself: flipping which origin serves requests doesn't automatically clear what the CDN has already cached from the old origin, so you need to explicitly invalidate stale cached content at the moment of cutover, or users can keep seeing old, cached responses even though the origin has switched.
Structured elaboration
- Warm the new origin's cache before cutover: if the CDN caches per-origin, priming commonly-requested paths against the green origin before it starts receiving real traffic avoids a cold-cache latency spike at the moment of cutover.
- CDN invalidation at cutover: for content that's origin-dependent (a page whose HTML or API response differs between blue and green), explicitly purge/invalidate the relevant cache keys at the CDN as part of the cutover step, not after, or users could be served a mix of old cached content and new origin responses inconsistently.
- DNS TTL for the origin switch itself: if the CDN's origin selection is driven by DNS, a short TTL for the cutover window ensures the CDN's edge nodes pick up the new origin promptly; a long, stale TTL from routine operation can mean some edge locations keep hitting the old origin well past when you believe the cutover completed.
- Session affinity: for anything relying on sticky sessions at the CDN or origin level, a user mid-session during cutover could have their session pinned to the OLD origin depending on how affinity is implemented; confirm whether session state is externalized (shared between blue and green) or whether affinity itself needs to be reset as part of cutover.
- What to check before the switch: the green origin is warm and passing health checks directly (bypassing the CDN, hitting it straight) so you're validating the origin itself, not a cached response. What to check after: sample real, CDN-fronted requests from multiple edge locations/regions to confirm they're actually hitting the new origin and getting fresh (not stale-cached) responses, since a purely origin-side health check wouldn't catch a CDN-layer caching problem.
Worked example
A cutover where the green origin's health check passes cleanly directly against the origin, but samples of CDN-fronted requests from three different edge regions ten minutes post-cutover show one region still serving a stale cached response, tracing back to a CDN edge node that hadn't yet honored the DNS TTL change. This is caught specifically by testing THROUGH the CDN from multiple locations, not just testing the origin directly, which is exactly why a pure origin health check isn't sufficient validation for a CDN-fronted cutover.
Trade-offs and pitfalls
The most common mistake is validating only the origin directly and declaring the cutover successful, missing that the CDN layer between users and the origin has its own state (cached content, possibly stale DNS resolution at the edge) that needs its own explicit verification. Aggressive cache invalidation at cutover trades a brief spike in origin load (as the CDN re-fetches everything fresh) for correctness; under-invalidating trades correctness for a smoother load profile, and getting that balance wrong in either direction has a real cost.
You operate a global service and want to do region-by-region staged rollouts to limit blast radius. How would you coordinate DNS, geo-routing, and multi-region orchestration, and what would you test before each region's rollout?
Sample Answer
Direct answer
Region-by-region staged rollout needs to coordinate the DNS/geo-routing layer that sends users to a region with the actual capacity and readiness of that region's new deployment, so a region only starts receiving live traffic on the new version once it's been independently validated, not just because the calendar step says "now roll out region 2."
Structured elaboration
- Deploy without exposing: roll the new version out to a region's infrastructure first WITHOUT shifting user traffic there yet, so you can validate it against synthetic or internal traffic before any real user in that region is affected.
- Geo-routing shift: use DNS-based geo-routing (with a suitably short TTL for the specific rollout window) or a global load balancer's region-weighting to gradually shift REAL user traffic for that region onto the newly-validated deployment, rather than an instant full cutover.
- Per-region validation before advancing: confirm the region's health (error rate, latency, any region-specific business metric) independently before starting the NEXT region's rollout; a region's traffic pattern, data-residency constraints, or infrastructure quirks can surface a bug that a different region's rollout wouldn't have caught.
- What to test before each region's rollout: region-specific configuration (any locale, currency, or regulatory-specific behavior), the region's actual infrastructure capacity for the new version's resource profile (a region with older or smaller instance types might not handle the same load the way a larger region does), and connectivity to any region-local dependencies (a regional database replica, a regional cache) that a different region's testing wouldn't have exercised.
- Order regions by risk: start with a lower-traffic or lower-stakes region rather than your largest market, so a regional-specific bug is caught on a smaller blast radius before reaching your highest-value region.
Worked example
A four-region service rolls out to its smallest region first (validated internally, then geo-routed traffic shifted over 24 hours while watching region-specific metrics), then the next-smallest, and so on, saving the largest region for last once the release has already accumulated real-world validation from three smaller regions. If the second region reveals a regulatory-specific data-handling bug unique to that region's compliance requirements, the rollout pauses there rather than proceeding to region three until it's fixed and re-validated, and regions one and two's rollout status is unaffected since they're independently tracked.
Trade-offs and pitfalls
This is meaningfully slower than a global simultaneous rollout, trading time for the ability to catch region-specific issues on a contained blast radius; the DNS-TTL consideration matters concretely, since a long cached TTL from a previous, unrelated DNS configuration can mean some users' geo-routing doesn't actually update as fast as the rollout plan assumes, so validating actual traffic-shift behavior (not just assuming DNS changes take effect instantly) is an important, easy-to-skip step.
What does 'zero-downtime deployment' mean in practice? Describe a rolling-update pattern that achieves it for a service that isn't running in Kubernetes: how do you manage traffic shifting, session affinity, connection draining, and graceful shutdown during the update?
Sample Answer
Direct answer
Zero-downtime deployment means users never experience a failed request or a visible interruption because of the deploy itself; achieving it outside Kubernetes on, say, a fleet of VMs behind a load balancer means the same core ideas Kubernetes automates for you (gradual replacement, draining, health-gating) but implemented explicitly with your own tooling.
Structured elaboration
- Traffic shifting: deregister an instance from the load balancer BEFORE touching it, not after; most load balancers (an ALB, an nginx/HAProxy config, a cloud LB) support this via a deregistration API or a health-check-based removal, giving in-flight connections time to drain before the instance stops receiving new traffic.
- Session affinity: if the load balancer uses sticky sessions, deregistering an instance means those users' NEXT request goes elsewhere and picks up a new session, which is fine for stateless session handling (session state in a shared store like Redis) but breaks in-memory session state tied to that specific instance; the real fix is externalizing session state rather than working around instance-level stickiness during deploys.
- Connection draining: after deregistration, wait a configured drain period (matching your load balancer's own connection-draining timeout) before actually stopping the old process, so in-flight requests complete rather than being cut off mid-response.
- Graceful shutdown: the application itself needs to handle a shutdown signal by finishing current requests and refusing new ones, rather than exiting immediately, working WITH the load balancer's drain period rather than assuming the LB alone solves it.
- Pattern: replace instances in small batches (say, one at a time or a small percentage), health-checking each replacement before removing the next old instance, mirroring what Kubernetes' rolling update does automatically but implemented via your own deploy scripts or configuration-management tooling.
Worked example
A fleet of 20 VMs behind an ALB: the deploy script deregisters instance 1 from the target group, waits for the ALB's configured drain timeout (say 30 seconds) for in-flight requests to complete, then stops the old process, deploys the new version, starts it, waits for it to pass a health check, and re-registers it, before moving to instance 2. This one-at-a-time approach is slow for 20 instances but maximizes safety; a batched version (5 at a time) trades some of that safety margin for speed.
Trade-offs and pitfalls
Doing this manually (without Kubernetes automating it) means you own building and testing the orchestration logic yourself, including the failure-handling: what happens if a replacement instance fails its health check, does the script halt and alert, or does it blindly continue removing more old instances regardless? The common mistake is deregistering an instance and immediately killing the old process without respecting the load balancer's actual drain timeout, which silently drops in-flight requests despite the deployment otherwise looking clean.
Unlock Full Question Bank
Get access to all Safe Deployment and Rollback Strategies interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.