Atomic deploys e cache: blue/green, cache headers, invalidation, rollback
5 min de leitura
Voce fez deploy, app quebrou em prod, corre pra reverter. Mas "reverter" demora 30min: build antigo, rollback, DNS switch, esperar propagar. Esse no cobre atomic deploys e cache: como deploy sem downtime (blue/green), cache headers corretos, invalidation estrategica, e rollback instantaneo.
Atomic deploy = nova versao substitui a antiga como unidade atomica. User sempre ve versao consistente (v1 OU v2, nunca mix). CDN + cache = 95% do TTFB vem de cache, 5% do origin. Cache strategy errada = user ve versao antiga apos deploy (cache stale) ou cache nunca funciona (TTFB alto).
Voce sai de "deploy = subir codigo e rezar" pra "deploy = zero downtime
- cache strategy".
O essencial 🟢
Os 3 problemas de deploy "nao- atomic".
- Mix de versoes. User recebe
index.html(v2) eapp.js(v1). App quebra (HTML novo espera JS novo). - Cache stale. User recebe versao antiga de CDN mesmo apos deploy novo.
- Rollback lento. "Reverter" = git revert + redeploy + esperar propagar (15-30min).
A solucao: atomic deploy. Nova versao inteira substitui a antiga. User sempre ve versao consistente. CDN/load balancer faz swap instantaneo (DNS, load balancer, ou feature flag).
Cache-Control headers - a base.
# Static asset COM hash no filename
Cache-Control: public, max-age=31536000, immutable
# (1 ano - pode ser cacheado pra sempre, hash garante freshness)
# HTML / SSR pages
Cache-Control: public, max-age=0, must-revalidate
# (sempre revalidar com servidor, CDN checa ETag)
# API response (GET, idempotente)
Cache-Control: public, max-age=60, stale-while-revalidate=300
# (60s fresh, 300s serve stale enquanto revalida)
# User-specific (auth)
Cache-Control: private, no-store
# (NAO cachear, user-specific)
# Mutation (POST/PUT/DELETE)
Cache-Control: no-store
# (NAO cachear response)
max-age = segundos ate o cache
expirar. stale-while- revalidate = serve stale enquanto
revalida em background (latencia
minima).
ETag - "cache fingerprint".
ETag: "abc123"
Cache-Control: public, max-age=0, must-revalidate
Server manda ETag. Browser
guarda. Proximo request: If- None-Match: "abc123". Server
responde 304 Not Modified (sem
body) se ETag bate = economia de
bandwidth.
Vary - "cache por criterio".
Vary: Accept-Encoding, Accept-Language
CDN cachea variantes por
Accept-Encoding (gzip vs
br), Accept-Language (en vs pt-BR).
Mesma URL, respostas diferentes
por header.
Content-hash em filename - "evita stale".
// Build gera:
app.a3b4c5.js (hash do content)
app.b9c2d1.css
// HTML referencia:
<script src="/app.a3b4c5.js"></script>
// Deploy novo:
app.e7f8g2.js (novo hash)
app.h4i5j6.css
// HTML referencia:
<script src="/app.e7f8g2.js"></script>
// Browser:
- v1: <script src="app.a3b4c5.js"> - cached
- v2: <script src="app.e7f8g2.js"> - novo request
Hash = cache busting automatic. CDN serve v1 forever (ate expirar em 1 ano). v2 e' novo request. Sem mix.
Vite, Webpack, esbuild fazem isso automatic. Configure:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
entryFileNames: "assets/[name].[hash].js",
chunkFileNames: "assets/[name].[hash].js",
assetFileNames: "assets/[name].[hash][extname]",
},
},
},
});
Atomic deploy com Vercel/Cloudflare Pages.
Vercel faz atomic por default:
- Cada deploy = novo immutable deployment.
- DNS switch instant.
- Rollback = 1 click no dashboard (volta pro deployment anterior).
- Sem downtime.
Cloudflare Pages similar: cada push = novo deployment, swap instant, rollback via dashboard.
Blue/green deployment.
- Blue: versao atual (v1), recebe 100% trafego.
- Green: nova versao (v2), recebe 0% (staging).
- Deploy: green pronto, switch DNS/load balancer.
- Switch: 100% trafego pra green.
- Rollback: switch de volta pra blue.
Zero downtime, rollback em segundos. Use com feature flag pra canary: 5% trafego pra green primeiro, monitor, scale up.
Canary deployment - "5% primeiro, depois 100%".
1. Deploy v2 como canary (5% trafego).
2. Monitor error rate (Sentry), latency, business metrics.
3. Se OK: scale 25% → 50% → 100%.
4. Se bad: rollback automatico (volta 100% pro v1).
Canary = blue/green com gradual rollout. Use feature flag ou load balancer com weight adjustment.
Cache invalidation strategies.
-
Time-based (
max-age): Automatic. Cache expira em N segundos. Use pra: API responses, dados que mudam periodicamente. -
Tag-based (
Cache-Tagheader- purge API): Tag arquivos/ responses, purge via API. Use pra: blog posts, produtos com update.
-
Content-hash (filename): Cache nunca invalida - novo arquivo = novo request. Use pra: JS/CSS/assets.
-
Surrogate-Key (Fastly/Cloudflare Enterprise): Tag + purge global. Use pra: multi-CDN.
Cache no Cloudflare.
// Cloudflare Workers - cache API
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const cache = caches.default;
const cached = await cache.match(request);
if (cached) return cached;
const response = await fetch(request);
const cacheable = new Response(response.body, response);
cacheable.headers.set("Cache-Control", "public, max-age=3600");
ctx.waitUntil(cache.put(request, cacheable));
return response;
},
};
Cloudflare cache = edge cache global. Cache HIT = response do edge, sem ir ao origin.
Vercel - purge cache.
# Vercel CLI
vercel cache purge --path=/api/products
Ou via dashboard: Settings > Data Cache > Purge.
Cache-Control em Next.js.
// next.config.js
async headers() {
return [
{
source: "/api/:path*",
headers: [
{ key: "Cache-Control", value: "public, max-age=60, stale-while-revalidate=300" },
],
},
{
source: "/_next/static/:path*",
headers: [
{ key: "Cache-Control", value: "public, max-age=31536000, immutable" },
],
},
{
source: "/:path*",
headers: [
{ key: "Cache-Control", value: "public, max-age=0, must-revalidate" },
],
},
];
}
3 rules: API (60s), static (1y), pages (revalidate).
CDN cache vs browser cache.
- Browser cache: user device. Por URL, por origin. Persistente.
- CDN cache: edge location (Cloudflare, Vercel). Por URL, por edge. Persistente (ate TTL).
- Origin cache: server (seu app). In-memory ou disk.
2 layers de cache (browser + CDN) = 95% das requests nao chega ao server. Custo reduzido 10x.
Atomic deploy + cache strategy = "deploy sem dor".
1. Build: gera app.<hash>.js
2. Test: roda E2E em preview
3. Atomic deploy: envia build pra CDN
4. CDN: invalida HTML (1 URL)
5. HTML novo: referencia app.<hash>.js (novo)
6. Browser: baixa HTML novo, descobre app.<hash>.js, baixa
7. CDN serve app.<hash>.js (cache miss 1x, depois hit)
8. Rollback: 1 click no Vercel, volta pro deployment anterior
Zero downtime, rollback instant, sem stale.
Aprofundamento 🟡
Surrogate-Key / Cache-Tag. CDN enterprise (Cloudflare, Fastly) tem tag-based purge:
// Cloudflare
response.headers.set("Cache-Tag", "product-123,category-electronics");
// Purge
await fetch(`https://api.cloudflare.com/.../purge_cache`, {
method: "POST",
body: JSON.stringify({ tags: ["product-123"] }),
});
1 purge = todas as URLs com aquela tag invalidadas. Use pra: CMS publica novo conteudo, atualiza produto, etc.
Service Worker + cache strategy.
// service-worker.ts
self.addEventListener("fetch", (event) => {
if (event.request.destination === "image") {
event.respondWith(
caches.open("images-v1").then((cache) =>
cache.match(event.request).then((cached) => {
return (
cached ||
fetch(event.request).then((response) => {
cache.put(event.request, response.clone());
return response;
})
);
})
)
);
}
});
Service Worker = cache custom no client. Stale-while-revalidate: serve cache, atualiza em background. Use pra: PWA, offline-first.
Cache poisoning - "cache entrega
conteudo errado". Risco: se
Vary header nao setado, CDN
cache user A response e serve
pra user B. Solucao: sempre
Vary: Cookie, Authorization em
response user-specific.
ETag vs Last-Modified.
- ETag: hash do content. Mais preciso (1 byte change = novo ETag). Use por default.
- Last-Modified: timestamp. Menos preciso (1s granularity).
Use ETag por default. Some servers (S3) usam ambos - preferem ETag.
Negative caching. Cache de
"404 Not Found" = economizar
requests repetidos pra URLs que nao
existem. Use com cuidado:
Cache-Control: public, max-age=60
em 404. Ataque: atacante faz
10K requests pra URLs inexistentes
= cache poisoned.
Layered cache.
- Browser cache (max-age=31536000, immutable) - 90% hit
- CDN cache (Cloudflare, max-age=86400) - 5% hit (após browser miss)
- Origin (server) - 5% reach
3 layers = 99% cache hit. Origin = backup only. Custo de origin drasticamente reduzido.
Cache hit ratio (CHR). CHR = % de requests servidos do cache. CHR 95% = excelente. CHR 80% = aceitavel. CHR < 70% = investigue.
Ferramentas: Vercel Analytics, Cloudflare Analytics, browser DevTools Network tab (size from cache).
Rollback strategies.
- Vercel dashboard: 1 click (rollback).
- Cloudflare Pages: dashboard ou API.
- Custom CI:
git revertcommit + redeploy. - Feature flag: toggle off (instant, no deploy).
- Blue/green: switch DNS pra blue (instant).
Feature flag rollback e' mais rapido (30s) que redeploy rollback (5-15min). Use feature flag pra componentes criticos.
Zero-downtime migration. Migrar DB sem downtime:
- Adicionar nova coluna (v2) sem remover v1.
- Codigo escreve em ambas (dual-write).
- Backfill v2 com dados v1 (background job).
- Codigo le de v2 (com fallback pra v1).
- Codigo para de escrever em v1.
- Drop v1 (cleanup).
6 steps = zero downtime, rollback em qualquer step.
Leitura recomendada:
- web.dev - HTTP Caching - best practices, headers, patterns.
- MDN - HTTP Cache Headers - referencia completa, Cache-Control, ETag.
- Cloudflare - Cache Rules - rules customizadas, tags, purge.
Dica: o erro mais comum em deploy e' "cache stale" (user ve versao antiga apos deploy). 3 fixes: (1) content-hash em filename (Vite/Webpack fazem automatic), (2) HTML sempre revalidate (
max-age=0, must-revalidate), (3) CDN purge HTML (NAO assets com hash). Sem isso = user ve JS/CSS novo + HTML antigo = app quebrado. **Atomic deploy
- cache strategy correta** = zero dor.
No proximo no (ultimo), vamos projeto final: deploy de SPA + edge function com preview por PR, ISR, image optimization, e atomic deploys. O "hello world" de apps em producao moderna.
// Quiz
Qual a diferenca entre 'atomic deploy' e 'incremental deploy', e por que atomic e' melhor para zero downtime?