Pular para o conteúdo principal

Caching

O template usa caching em camadas para performance e resiliência no Cloudflare Workers. Esta página descreve o estado vigente após o cache overhaul (Fases 0-5).

Para a visão sistêmica de "como tudo se encaixa" (incluindo front-service-api e o browser), veja Arquitetura > Cache architecture. Para "como invalidar X em produção", veja Infraestrutura > Cache operations.

:::info Mudança recente (cache overhaul) Três coisas mudaram em relação à versão anterior:

  1. api-client server-side foi desabilitado no brand worker via serverCache.delegateCaching: true. A única camada de cache de dados de API é o platform-cache engine — fim da duplicação que causava postmortems de staleness (/payment-providers, /appearance).
  2. Cache tags foram introduzidas. Cada resource declara tags: [...] em front-ops/config/cache/defaults.yml. Permite purges como tags: ["brand"] ou glob: "games:*".
  3. TTL do brandConfig caiu pra 60s + SWR 300s (era 3600s). Mudanças via backoffice / overrides refletem em ≤1min sem precisar de purge manual. :::

Camadas de cache

Requisição
|
v
[1] CF Zone Edge — SSR HTML/.data (CF Cache API + KV "ssr:${BUILD_ID}")
| TTL: HTML 60s + SWR 120s · .data 30s + SWR 30s
| Invalida via: BUILD_ID bump (auto no deploy) ou zone purge_everything
v
[2] Brand Worker Middleware — API gateway cache (rotas /api/*)
| Whitelist: /api/casino-games/*, /api/appearance, /api/country, etc
| TTLs hardcoded em workers/middleware.ts
v
[3] platform-cache engine — dados lógicos (memory → CF Cache API → KV snapshot)
| Policy: PLATFORM_CACHE_POLICY_JSON (do front-ops)
| Tags: brand, catalog, games:*, stats, legal, payments, sports, seo
| Tier 1 (memory): per-isolate
| Tier 2 (CF Cache API): per-DC, namespace "platform-cache-apcsrasj-v3"
| Tier 3 (KV snapshot): cross-DC, opcional por resource
v
[4] front-service-api proxy worker (Frankfurt, Smart Placement)
| CF Cache API + KV API_CACHE_KV
| TTLs por path: SERVICE_API_CACHE_POLICY_JSON (do front-ops)
v
[5] BFF

Camada 1 — SSR Response Cache (Worker Middleware)

Cacheia HTML de SSR (e .data do RR7) no edge da Cloudflare para visitantes anônimos. Implementação em workers/middleware.ts.

Regras

CondiçãoComportamento
Método ≠ GETBYPASS
Path começa com /api/, /proxy/API cache (camada 2)
Cookie jwt_token presenteSSR cache autenticado (chave por sessionHash)
Path é asset (/assets/*)Asset cache (1y immutable + R2 archive fallback)
Demais paths whitelistedSSR cache público

Política

  • Anônimo: Cache-Control: public, s-maxage=60, stale-while-revalidate=120
  • Autenticado: private, s-maxage=60, stale-while-revalidate=120 (chave inclui sessionHash do JWT)
  • Header X-Cache: HIT-CACHE | HIT-KV | MISS-API | BYPASS-API para debug

KV namespace e KV_SSR_TTL

Namespace: ssr:${BUILD_ID} (CF Cache API) e key ssr:${BUILD_ID}:${md5(method:url)} (KV).

O BUILD_ID (12-char commit SHA) muda a cada deploy → entries antigas ficam órfãs e expiram naturalmente dentro do KV_SSR_TTL.

KV_SSR_TTL é a constante que determina quanto tempo o HTML SSR anônimo sobrevive em KV. Foi reduzida de 12h para 1h (3600s) — esse era o motivo real de banners/mudanças de brand demorarem horas a aparecer após um deploy (o brandConfig já era 60s, mas o HTML cacheado em KV continha o banner baked da renderização antiga).

Com KV_SSR_TTL = 3600, o HTML fica stale por no máximo 1h antes de ser reaprovisionado. Para sub-1h (ex: banner deve aparecer em segundos), é necessário um zone purge ou excluir a rota home do SSR-HTML caching. Não precisa de purge manual desta camada para uso normal. Para invalidar tudo no edge, use zone purge_everything (acionado automaticamente pelo deploy ou via manual-cache-purge.yml scope: zone).

Smartico Proxy

/proxy/smartico.js:

  • Origem: https://libs.s.cactusgaming.net/scactus.js
  • Cache: s-maxage=7200, stale-while-revalidate=86400
  • CORS: Access-Control-Allow-Origin: *

Em dev, vite-plugins/dev-proxy-mirror.ts espelha a rota no Vite. Brands devem apontar gamificationConfig.smartico.libraryUrl para /proxy/smartico.js.

Camada 2 — API gateway cache (rotas /api/*)

Cacheia GETs para um conjunto whitelisted de rotas internas /api/* que fazem proxy ao BFF. Independente da camada 3 (platform-cache).

Ceiling 1h: todos os TTLs desta camada estão limitados a 3600s. Um runtime Math.min(ttl, 3600) em getApiCacheRule garante que edits futuros não possam reintroduzir TTLs maiores.

RotaTTL
/api/casino-games/*1h
/api/appearance1h
/api/country1h
/api/configurations/casino1h
/api/bff/features1h
/api/bff/games/top-wins1h
/api/bff/games/last-wins1h
/api/bff/games/statistics1h
/api/cactus-sportbook/search5min

Implementação: workers/middleware.ts:441-467. Namespace api:${BUILD_ID}.

Camada 3 — platform-cache engine

Cache lógico de domínio (resources como brandConfig, homeRows, gameStats). Detalhes completos no SDK: @cactus-agents/platform-cache.

Wire-up no brand worker

import {
createCacheEngine,
parseCachePolicy,
CacheApiStore,
KvSnapshotStore,
NoopSnapshotStore,
} from "@cactus-agents/platform-cache";

const engine = createCacheEngine({
policy: parseCachePolicy(env.PLATFORM_CACHE_POLICY_JSON),
primaryStore: sharedPrimaryStore, // singleton CacheApiStore module-level
snapshotStore: env.PLATFORM_CACHE_KV
? new KvSnapshotStore(env.PLATFORM_CACHE_KV)
: new NoopSnapshotStore(),
domain: env.CACHE_NAMESPACE ?? env.ORIGIN_DOMAIN,
});

TTLs canônicos (PROD)

Definidos em front-ops/config/cache/defaults.yml. Resumo:

ResourceTTLSWRKVTags
brandConfig60s300sSimbrand, config
homeRows3600s600sSimcatalog, rows, games:list, home
casinoRows3600s600sSimcatalog, rows, games:list, casino
casinoLiveRows3600s600sSimcatalog, rows, games:list, casino, live
gamesBase3600s300sSimcatalog, games:base
allGames3600s600sSimcatalog, games:list, games:all
topGames3600s600sSimcatalog, stats, games:high-payers
gameStats1800s1800sNãocatalog, stats, games:stats
gameDetail3600s600sNãocatalog, games:detail
gameCategory3600s600sNãocatalog, games:category
gameProvider3600s600sNãocatalog, games:provider
gameListPage300s300sNãocatalog, games:list, games:listPage
gameTopWins300s120sNãocatalog, stats, games:wins
topWins1800s120sNãocatalog, stats, games:wins
lastWins1800s120sNãocatalog, stats, games:wins
legalTerms3600s300sNãolegal, config

Brands podem fazer override por env em config/brands/<brand>/environments/<env>/cache.yml (full replace por nível).

Pipeline de fetch

  1. Primary store (memory → CF Cache API)
  2. Se cache-only, pára aqui
  3. KV snapshot (se fallbackEnabled: true)
  4. Origin fetch
  5. Em erro de origin: retry primary com staleIfError grace
  6. Em erro de origin sem cache: KV snapshot last-resort

Single-flight coalescing por isolate evita thundering herd. SWR escreve resultado novo em background via ctx.waitUntil.

Camada 4 — service-api proxy

front-service-api é um Worker separado em Frankfurt (Smart Placement) chamado via Service Binding API_SERVICE. Cacheia respostas HTTP do BFF em CF Cache API + KV.

TTLs e tags em front-ops/config/cache/service-api-defaults.yml, injetados como SERVICE_API_CACHE_POLICY_JSON no deploy do worker (via pnpm deploybuild-cache-policy.mjs).

Não chama-se direto — apenas via Service Binding.

Camada 5 — Client-side (api-client localStorage)

Brand worker tem serverCache.delegateCaching: true, então o api-client não cacheia nada no server. No browser, o api-client mantém um LocalStorageCache com TTL curtos (60s) só pra UX offline:

  • cac:api:<method>:<url> em localStorage
  • Limpa em logout (purgeAllClientCaches)
  • Geração unificada apcsrasj-v3 — um bump de prefix invalida entradas do browser localStorage na próxima visita

Purge

Endpoints

EndpointMétodoAuthFunção
/api/cache/purgePOSTX-Cache-SecretPurge platform-cache + cascade service-api. Aceita segments, tags, glob, only
/api/cache/inspectGETX-Cache-SecretEstado live do platform-cache (TTL restante por entry)
/api/dev/cache-policyGETopen em devLista da policy carregada
/api/dev/cache-clearPOSTsó em devpurgeAll()

Body do /api/cache/purge

// Por tags (preferido)
{ "tags": ["catalog", "brand"] }

// Por glob de tag
{ "glob": "games:*" }

// Por segments (legado, ainda funciona)
{ "segments": ["homeRows", "casinoRows"] }

// Scope (gate qual camada atua)
{ "tags": ["brand"], "only": "platform-cache" } // só engine local
{ "tags": ["brand"], "only": "service-api" } // só proxy
// (omitir "only" = ambos em paralelo, padrão)

// Sem nada = purgeAll
{}

Workflows GH Actions

WorkflowTriggerFunção
manual-cache-purge.ymlworkflow_dispatchPurge ondemand. Inputs: environment, tags/glob/segments, scope, dry_run
cache-status.ymlworkflow_dispatchInspeciona estado live (TTLs restantes, tags por resource)
deploy.ymlpush / dispatchInclui post-deploy purge + warmup expandido (10 paths)

Env vars

VariávelFunção
PLATFORM_CACHE_POLICY_JSONPolicy serializada do brand worker. Injetada pelo front-ops. Ausente = BYPASS mode
PLATFORM_CACHE_KVBinding KV para snapshot tier. Provisionado automaticamente
CACHE_NAMESPACEWorker name — isola stage/prod que compartilham ORIGIN_DOMAIN
CACHE_PURGE_SECRETAuth do /api/cache/purge e /api/cache/inspect. Mirrored no service-api
SERVICE_API_CACHE_POLICY_JSONRules do proxy worker. Injetada pelo pnpm deploy do front-service-api
BUILD_ID12-char SHA do deploy. Usado em namespace do SSR cache e header X-LOG-INFO

Arquivos

ArquivoDescrição
workers/middleware.tsSSR/API/asset cache + Smartico proxy
app/services/platform-cache.server.tsSingleton sharedPrimaryStore + factory do engine
app/services/brand.server.tsengine.fetch("brandConfig", ...)
app/services/games.cache.server.tsengine.fetch("homeRows", ...) etc — todos os resources de games
app/services/api.server.tscreateCactusServerClient com delegateCaching: true
app/routes/api/cache/purge.tsEndpoint de purge (tags/glob/segments)
app/routes/api/cache/inspect.tsEndpoint de introspecção live
app/routes/api/dev/cache-policy.tsValidação da policy carregada