Roll a new version out to a small slice of traffic first, and only expand it once it's proven healthy under real usage.
Even careful pre-release testing doesn't always catch every problem — some issues only appear under real production traffic patterns or scale. Switching every user to a new version at once means a bad deploy affects everyone simultaneously before anyone notices.
The new version is deployed alongside the old one, and the router sends only a small percentage of traffic to it initially (the "canary"). Metrics are watched closely; if healthy, the percentage is increased in steps until it reaches 100%. If something looks wrong at any step, traffic is routed back to the old version entirely.
A small, deterministic slice of requests goes to the canary version. The ramp function only increases that slice if the canary's error rate looks healthy.
let canaryPercent = 5;
function routeRequest(requestId) {
const bucket = hashToPercent(requestId); // deterministic 0-99 for a given request
return bucket < canaryPercent ? "v2 (canary)" : "v1 (stable)";
}
function hashToPercent(id) {
let h = 0;
for (const c of id) h = (h * 31 + c.charCodeAt(0)) >>> 0;
return h % 100;
}
function maybeRampUp(canaryErrorRate) {
if (canaryErrorRate < 0.01 && canaryPercent < 100) {
canaryPercent = Math.min(100, canaryPercent * 2);
console.log(`canary healthy — ramping up to ${canaryPercent}%`);
} else if (canaryErrorRate >= 0.01) {
canaryPercent = 0;
console.log("canary unhealthy — rolled back to 0%");
}
}
console.log(routeRequest("req-101"));
maybeRampUp(0.002); // healthy → ramps 5% to 10%