Cache operations (playbooks)
Esta página é o manual de operações pro cache da plataforma. Cada seção é um playbook independente — entrar, fazer, sair.
Para entender por que o sistema é assim, veja Cache architecture. Para a visão de "como funciona, sem detalhes técnicos", veja Cache strategy.
Pré-requisitos
- Acesso ao repositório
cactus-agents/front-opsno GitHub - Permissão pra rodar Actions workflows
- Em casos críticos: acesso ao Cloudflare Dashboard (CT e/ou BT)
Todos os exemplos abaixo assumem state77-com como environment. Substitua pelo seu caso — a lista viva de environments é front-ops/config/brands/*/environments/, e é ela que popula o dropdown do purge.
Os workflows de cache do front-ops
| Workflow | Trigger | Pra que serve |
|---|---|---|
| Manual Cache Purge | manual | Purge cirúrgico por tag/glob/segmento numa env (playbooks 1-4) |
| Cache Status | manual | Inspeção read-only do estado do cache (playbook 5) |
| Purge All | manual ou chamado por outro workflow | Purge total de uma env em "two-pass reverse-order" |
| Bump cache generation | manual | Rotaciona o token de generation da frota inteira (playbook 11) |
| Purge Monitor | cron */5 * * * * | Detecta drift de shape do BFF e dispara Purge All sozinho |
| Sync purge options | push em main + cron diário | Abre PR regenerando os dropdowns do Manual Cache Purge |
:::info Por que aparece uma PR automática quando você adiciona uma env ou tag
Os dropdowns environment e tag_quickpick do manual-cache-purge.yml são YAML estático — o GitHub não os popula do filesystem ao renderizar a UI. O sync-purge-options.yml roda a cada push em main que toca config/brands/*/environments/**, config/cache/defaults.yml ou config/cache/service-api-defaults.yml (mais um cron diário às 09:00 UTC) e abre uma PR quando os dropdowns divergem da realidade. Mergeie a PR, ou o purge da env nova não fica selecionável.
:::
Inputs do Manual Cache Purge
| Input | Default | Nota |
|---|---|---|
environment | — (obrigatório) | Dropdown com todas as envs. Regenerado pelo sync-purge-options.yml |
tag_quickpick | (none — use tags/glob below) | Dropdown curado de tags. Preenche tags quando tags está vazio |
tags | vazio | CSV livre (ex: payments,providers). Vence o tag_quickpick quando preenchido |
glob | vazio | Glob de tag (ex: games:*). Mutuamente exclusivo com tags |
segments | vazio | CSV de resource keys (avançado). Vazio = purgeAll |
scope | platform-cache | platform-cache / service-api / zone / all |
dry_run | false | Resolve e imprime a config sem chamar a API |
:::caution O default de scope é platform-cache, não all
Todo playbook abaixo que pede scope: all exige que você mude o dropdown. Se você deixar no default, só o brand worker é purgado — service-api e zone ficam intactos, e o sintoma volta.
:::
Playbook 1 — Invalidar config de brand (banners, payment-providers)
Quando usar: mudança no backoffice (banner novo, ordem de payment-providers, feature flag) que não está aparecendo após 1-2min.
Tempo: ~30s.
Passos:
- GitHub →
cactus-agents/front-ops→ Actions → Manual Cache Purge → Run workflow - Inputs:
- environment:
state77-com(ouprod-7k-bet-br, etc) - tag_quickpick:
brand(ou tags:brand) - scope:
all← mude do defaultplatform-cache
- environment:
- Run
- Aguarda ~30s. O step summary mostra resposta de cada camada.
- Valida visitando o site em janela anônima (pra contornar browser cache).
O que acontece por trás:
- Brand worker
engine.purgeByTag("brand")invalidabrandConfig(e qualquer outro resource com tagbrand) - Cascade pro service-api purga entradas com tag
brand(/v2/bff/features,/v2/appearance,/v2/bookmaker-settings,/v2/configurations/casino,/v2/country) - Próxima request → BFF → dado fresco
Por que isso é suficiente:
- TTL do brandConfig é 60s; este purge é só pra forçar antes do TTL natural expirar
- Não precisa zone purge nem CF Cache Reserve
Playbook 2 — Invalidar catálogo de jogos
Quando usar: jogo novo adicionado, jogo removido, mudança de ordem em categorias.
Tempo: ~30s.
Passos:
- Actions → Manual Cache Purge → Run workflow
- Inputs:
- environment:
state77-com - glob:
games:*(cobregames:list,games:detail,games:wins,games:stats,games:high-payers,games:category,games:provider,games:base,games:listPage,games:all,games:home,games:votes) - scope:
all
- environment:
- Run.
Variante (mais cirúrgica):
Pra atingir só home + casino (não detalhe de jogo):
- tag_quickpick:
rows - scope:
all
Pra atingir só a estatística "pagou hoje":
- tag_quickpick:
stats - scope:
all
O vocabulário completo de tags está no dropdown tag_quickpick — ele é gerado a partir das tags declaradas em config/cache/defaults.yml e config/cache/service-api-defaults.yml.
Playbook 3 — "O site inteiro tá com dados velhos, quero zerar tudo"
Quando usar: ultima opção. Postmortem em andamento, problema generalizado, ou após uma mudança grande no BFF.
Tempo: ~1-2min (incluindo zone purge).
Passos:
- Actions → Manual Cache Purge → Run workflow
- Inputs:
- environment:
state77-com - (deixa tags/glob/segments vazios) = purgeAll
- scope:
all
- environment:
- Run.
O que acontece:
- platform-cache
engine.purgeAll()no brand worker - service-api purga TODAS as entries com o
originDomainda env - CF zone
purge_everything(apaga TODO o cache de zone — assets, HTMLs, .data)
Cuidado: zone purge_everything tem custo CF e é nuclear. Use só quando realmente necessário.
Variante mais agressiva: o workflow Purge All faz um two-pass reverse-order purge — várias rodadas, camadas em ordem inversa, sem set -e (todas as etapas tentam mesmo que uma falhe). É o que o Purge Monitor dispara automaticamente em drift, e o que o cutover pós-warm do deploy roda. Use quando o purge simples não convergiu.
Playbook 4 — Invalidar só uma camada (debug)
Quando usar: investigando se um problema específico está em uma camada ou outra.
Inputs:
- scope: platform-cache — só invalida no brand worker (memory + CF Cache API + KV snapshot do platform-cache). É o default.
- scope: service-api — só invalida no proxy worker (KV
API_CACHE_KV+ CF Cache API do service-api) - scope: zone — só CF zone purge (edge cache do SSR HTML)
Use cases típicos:
- "Dados que vêm da home tão velhos, mas detalhe tá certo" →
scope: platform-cache, tags: rows - "Pagamentos errados no checkout" →
scope: service-api, tags: payments - "Página /alguma-coisa tá com HTML antigo mesmo após /api/cache/purge" →
scope: zone
Playbook 5 — Diagnosticar staleness
Quando usar: "tá dando velho mas eu não sei onde", antes de chutar um purge.
Tempo: ~10s.
Passos:
- Actions → Cache Status → Run workflow
- Inputs:
- environment:
state77-com - include_state:
true(default — probe live state, mais lento) - tag / glob: opcionais, pra filtrar
- environment:
- Run.
- Abrir o step summary. Vai mostrar uma tabela com:
- Qual resource está warm em CF Cache API
- Quantos segundos de age (desde a última população)
- Quantos segundos fresh sobram antes de virar stale
- Quais tags cada resource tem
Interpretação:
Primary hit ❌em vários resources → cache está cold, próxima request vai pagar custo de BFFAge > TTLmasFresh? ⚠️→ resource está stale, SWR vai revalidar na próxima request- Resource esperado não aparece → não está declarado na policy (BYPASS silencioso). Veja Playbook 7.
:::tip Antes de chutar um purge
Rode o Manual Cache Purge com dry_run: true primeiro. Ele resolve e imprime exatamente qual env, quais tags e quais camadas seriam atingidas, sem chamar nenhuma API. Custa 10s e evita um purge_everything acidental.
:::
Playbook 6 — Inspeção direta via curl
Pra automação, scripts, ou quando o GitHub Actions tá fora:
# Variáveis (substituir)
TARGET=state77.com
SECRET=$FRONT_BT_CACHE_PURGE_SECRET # a conta CF da env — state77-com é BT
WORKER_KEY=$FRONT_BT_CF_WORKER_KEY
# 1) Inspecionar policy declarada
curl -s "https://$TARGET/api/dev/cache-policy" \
-H "X-Cache-Secret: $SECRET" | jq .
# 2) Inspecionar estado live (com TTLs restantes)
curl -s "https://$TARGET/api/cache/inspect?include=state" \
-H "X-Cache-Secret: $SECRET" | jq .
# 3) Inspecionar só uma tag
curl -s "https://$TARGET/api/cache/inspect?tag=brand&include=state" \
-H "X-Cache-Secret: $SECRET" | jq .
# 4) Purge por tags
curl -s -X POST "https://$TARGET/api/cache/purge" \
-H "X-Cache-Secret: $SECRET" \
-H "cf-worker-key: $WORKER_KEY" \
-H "Content-Type: application/json" \
-d '{"tags": ["brand", "catalog"]}' | jq .
# 5) Purge tudo da brand (incluindo service-api)
curl -s -X POST "https://$TARGET/api/cache/purge" \
-H "X-Cache-Secret: $SECRET" \
-H "cf-worker-key: $WORKER_KEY" \
-H "Content-Type: application/json" \
-d '{}' | jq .
:::caution Envs atrás de CF Access
Nos stage-* o hostname fica atrás do Cloudflare Access e esses curl não passam sem um service token. Nesses envs o deploy.yml costuma ter skip_post_deploy_purge: true justamente por isso — e a invalidação real vem da rotação de CACHE_GENERATION a cada deploy.
:::
Playbook 7 — Adicionar resource novo ao platform-cache
Quando usar: novo serviço/módulo precisa cachear dados do BFF de forma agregada.
Tempo: ~15min, incluindo PR review.
Passos:
-
Define o resource no
front-ops/config/cache/defaults.yml:myNewResource:enabled: truettlSeconds: 600 # 10minstaleWhileRevalidateSeconds: 300staleIfErrorSeconds: 86400fallbackEnabled: false # ou true se for hotstorage:primary: cache_apisnapshot: none # ou kvkeyPrefix: myNewResourcetags: ["catalog", "my-domain"] -
Wire-up no service do brand worker:
// app/services/my-new.cache.server.tsexport async function getMyData(deps: Deps) {const result = await engine.fetch("myNewResource",() => fetchFromBff(deps),{ waitUntil: deps.ctx?.waitUntil },);return result.data;} -
(Opcional) Adicione ao service-api se o BFF path precisar de cache no proxy também.
Em
front-ops/config/cache/service-api-defaults.yml, dentro do blocorules::rules:/v2/my/bff/path:ttl: 1800methods: ["GET"]tags: ["catalog", "my-domain"]As
tagssão o que conecta as duas camadas: é assim que omanual-cache-purgepassa a mesma tag pro brand worker e pro proxy. -
PR em
front-opscom as duas mudanças. Se você adicionou uma tag nova, osync-purge-options.ymlvai abrir uma segunda PR atualizando otag_quickpick. -
Deploy explícito do brand worker. Um push no
front-opsnão redeploya o brand worker — o worker lêPLATFORM_CACHE_POLICY_JSONem deploy-time. Rode o deploy manualmente (ou aguarde o próximo deploy de código). -
Valida rodando
Cache Status— o novo resource deve aparecer na tabela.
Atenção:
- Resource user-scoped? Use
fetchWithKey(resource, userId, fetcher)e adicionetags: ["user-scoped"]pra documentar. Nunca usefetch()sem suffix em resource per-user. - KV ON (
fallbackEnabled: true+snapshot: kv) é caro (KV writes custam $). Use só pra resources hot ou pra cross-DC warm-up crítico.
Playbook 8 — Deploy não invalidou cache esperado
Sintoma: após deploy, mudança no código não aparece no site.
Diagnóstico:
- Verifica o run do
deploy.yml. Procura por:Skip purge notice (when configured)— se apareceu, o env temskip_post_deploy_purge: truee nenhum purge por segmento rodou (comportamento esperado nesse env)Purge platform cache after deploy— status 200?Purge SSR cache (CF zone purge)— status 200?Warm up cache— todos 200/304?Probe served build id (observational orphan check)— obuildIdservido bate com o deste deploy? Se não, há versão órfã atendendo tráfego
- Se algum step falhou: re-roda
Manual Cache Purgecomscope: all.
Causas comuns:
FRONT_{XX}_CACHE_PURGE_SECRETmismatch entre worker e workflow → 401 no stepcf_zoneerrado emdeploy.yml→ zone purge skip (warning)- Token CF sem permissão
Cache Purge:Purge→ 403 - Env atrás de CF Access com
skip_post_deploy_purge: true→ purge nunca roda por design - Versão órfã do Worker servindo parte do tráfego → ver
Verify single-version deployment
Veja config/brands/README.md no front-ops pro checklist completo de tokens CF.
Playbook 9 — Resource em BYPASS silencioso
Sintoma: dev adicionou engine.fetch("foo", ...) mas o resource não está cacheando.
Diagnóstico:
- Roda
Cache Statusna env. - Procura
foona tabela. - Se não aparece, é BYPASS — não está na policy.
- Logs do worker (
wrangler tail) deveriam mostrar uma vez:[platform-cache] resource "foo" has no policy entry — running in BYPASS mode
Solução: PR no front-ops/config/cache/defaults.yml (Playbook 7).
Playbook 10 — Forçar invalidação direta no Cloudflare (último recurso)
Quando usar: workflows do GH down, ou cache.purge endpoint do brand worker quebrado.
Tempo: ~2min.
Passos:
- Cloudflare Dashboard → Zone (ex:
state77.com) → Caching → Configuration - Purge by URL se sabe exatamente o que zerar, ou
- Purge Everything se é nuclear.
OU via API:
# Zone ID (lookup)
curl -s -X GET \
"https://api.cloudflare.com/client/v4/zones?name=state77.com&account.id=$ACCOUNT_ID" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[0].id'
# Purge everything
curl -s -X POST \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"purge_everything": true}'
Atenção: isso afeta SÓ o CF zone cache (SSR HTML em edge). Não toca platform-cache nem service-api. Pra purgar app-level use o Manual Cache Purge.
Playbook 11 — Rotacionar a cache generation da frota
Cenário: uma mudança de schema do BFF fez com que payloads cacheados em KV/CF Cache fiquem com formato incompatível com a nova versão do código — e você precisa que todas as camadas de todos os workers passem a usar um namespace novo e vazio.
Como funciona:
Existe um único token de generation. Ele é o prefixo de todos os namespaces de cache de longa duração da frota:
| Camada | Namespace derivado |
|---|---|
| Brand worker — platform-cache (CF Cache API) | platform-cache-<token> |
| Brand worker — api-client (LocalStorageCache no browser) | api-cache-<token> |
front-service-api — KV md5 prefix + CF Cache API | <BUILD_KEY>-<token> |
Trocar o token torna toda entry anterior inalcançável (não deletada — ela é evicted por LRU/TTL naturalmente).
Fonte de verdade operacional: front-ops/config/cache/generation.yml, campo
current. O arquivo mantém o histórico de todos os bumps com data e motivo.
:::danger Não edite generation.yml à mão, e não edite as constantes do core
As constantes em front-cactus-core/packages/platform-cache/src/generation.ts
(DEFAULT_CACHE_GENERATION), packages/api-client/src/cache.ts
(DEFAULT_CACHE_KEY_PREFIX) e front-service-api/src/index.ts
(DEFAULT_GLOBAL_VERSION) são fallbacks in-source, usados só quando nenhum
valor é injetado por env var. O próprio comentário do arquivo do core pede pra
deixá-las quietas e usar o workflow. Bumpar essas constantes por PR não é o
caminho — exige publicar pacote novo e não afeta o service-api já deployado.
:::
Passos:
-
Actions → Bump cache generation → Run workflow
-
Inputs:
Input O que faz reasonobrigatório. Vai pro commit message, pro histórico do generation.ymle pro step summary. Escreva algo que faça sentido em 3 mesestarget_valueopcional. Vazio = auto-incrementa o sufixo numérico do valor atual brand_workersnone/single/all— quais brand workers redeployar depois do bumpsingle_environmentslug da env, quando brand_workers: singleredeploy_service_apidefault true— dispara o deploy dofront-service-apiem CT e BT -
Run. O workflow computa o próximo valor, commita
generation.yml(com o PATFRONT_GH_ACTIONS_TOKEN) e dispara os deploys escolhidos. -
Aviso pré-deploy ao time: vai ter cold-start massivo. Faça em horário de baixo tráfego.
:::info Todo deploy já rotaciona a generation
O step Patch wrangler.toml with runtime vars monta o valor efetivo como
<generation.yml::current>-<BUILD_ID>. Como BUILD_ID são os 12 primeiros chars
do SHA deployado, cada deploy já entra num namespace novo. Ou seja: para
invalidar o cache de um worker, um deploy normal basta.
Este playbook só é necessário para o caso cross-deploy, frota inteira — quando você quer que workers que não vão ser redeployados também mudem de namespace, ou quando quer flipar o service-api junto.
Existe ainda um terceiro caminho, automático: em envs com run_warm_cutover: true,
o deploy bumpa o secret CACHE_GENERATION_SECRET do worker (sem rebuild) logo
depois do collector de warm, e então roda o purge-all multi-camada.
:::
Quando NÃO fazer: se um purge normal (playbooks 1-3) ou um simples redeploy resolveria. Rotação de generation é última opção.
Sumário rápido — escolha o playbook certo
| Situação | Playbook |
|---|---|
| Backoffice mudou e não reflete | 1 |
| Jogo novo não aparece | 2 |
| "Tá tudo errado, zera tudo" | 3 |
| Debug — quero saber em qual camada está o problema | 4, 5 |
| Inspeção rápida via curl | 6 |
| Adicionar resource novo | 7 |
| Deploy não invalidou esperado | 8 |
engine.fetch("foo") não cacheia | 9 |
| Workflow GH fora, preciso resolver via CF direto | 10 |
| Mudança de schema do BFF quebrou caches da frota inteira | 11 |