TL;DR
Em VTEX IO, o cache em memória (LRU do node-vtex-api) e o cache em disco
(DiskCache/LRUDiskCache, ex.: /cache, /usr/local/data) são por-pod e
efêmeros. Não existe cache de disco compartilhado entre pods — nem entre pods
co-localizados no mesmo nó do Kubernetes. Os diretórios graváveis do app são
montados como volumes emptyDir, que o Kubernetes cria vazios por pod e
descarta quando o pod morre.
Consequência para o rewriter: para ter cache cross-pod (o que precisamos para
tirar carga do License Manager em rollout), a única opção suportada é um store
compartilhado — VBase. Ver docs/binding-cache-vbase-proposal.md.
A hipótese investigada
Surgiu a dúvida: "se dois pods do mesmo app caem no mesmo nó, eles não
compartilhariam o disco (/cache)? Aí um LRUDiskCache já daria cache
cross-pod de graça."
A resposta é não. Abaixo, a trilha de evidências.
Evidências (código das plataformas)
1. Quais diretórios o app pode escrever
service-runtime-node (via service-runtime-base) define os diretórios graváveis
do processo do app e faz chown para o usuário do app:
_10// vendor/github.com/vtex/service-runtime-base/runner/run_child_app.go_10// appWritableDirs are volume mounts that the app process may write to._10// The service-runtime-base is created as root (UID/GID 0); we chown_10// them to app uid:gid (1000:1000) so the child can use /tmp, /cache, /run._10var appWritableDirs = []string{"/tmp", "/cache", "/run"}
E o diretório de dados do app:
_10// vtex/app-operator/controllers/deployment/container/container.go_10envvar.Plain("VTEX_DATA_DIR", "/usr/local/data"),
2. Como esses diretórios viram volumes
O app-operator declara os volumes do deployment. /usr/local/data, /cache,
/tmp e /run são todos DataSpec:
_10// vtex/app-operator/controllers/deployment/volume/volume_mounts.go_10volumeSpecs := []Spec{_10 &DataSpec{ Name: "volume-data", Path: "/usr/local/data" },_10 &DataSpec{ Name: "volume-cache", Path: "/cache" },_10 &DataSpec{ Name: "volume-tmp", Path: "/tmp" },_10 &DataSpec{ Name: "volume-run", Path: "/run" },_10}
3. O ponto-chave: DataSpec vira emptyDir
O DataSpec.volume() monta um corev1.Volume só com Name, sem hostPath,
sem PersistentVolumeClaim, sem nfs:
_10// vtex/app-operator/controllers/deployment/volume/data_spec.go_10func (spec *DataSpec) volume() corev1.Volume {_10 return corev1.Volume{_10 Name: spec.Name,_10 }_10}
No Kubernetes, um Volume sem source explícito default para emptyDir{}. E
emptyDir, por definição:
- é criado vazio quando o pod é agendado no nó;
- é exclusivo daquele pod (não é compartilhado com outros pods, mesmo no mesmo nó);
- é apagado quando o pod é removido do nó.
Conclusão
- Memória (LRU) → por-pod, efêmero. Cada réplica tem seu próprio heap.
- Disco (
/cache,/usr/local/dataviaDiskCache/LRUDiskCache) → por-pod, efêmero. ÉemptyDir, nãohostPath/PVC → não há disco compartilhado no nó. - Logo, nenhuma camada local dá cache cross-pod. Num rollout, cada pod novo sobe com cache frio e vai à origem (ex.: License Manager) sozinho.
Nota: a plataforma suporta disco compartilhado (
PVC ReadWriteMany) em casos específicos — ex.: o build cache dobuilder-hub. Mas isso é configuração dedicada daquele serviço; os pods de app comuns (como o rewriter) usamemptyDir.
Implicação para o rewriter
Para reduzir as chamadas ao License Manager entre pods (e domar os 429 MaxRequestsByInstance durante rollout), precisamos de um store compartilhado
entre pods. A escolha suportada e barata é o VBase (escopado por
conta/workspace), usado como camada intermediária, com uma LRU em processo por
cima para o hot path. Detalhes em docs/binding-cache-vbase-proposal.md.