Armazenamento em cache encadeado

Visão geral

Fluxo de dados

A entrega de uma página de um servidor para o navegador de um cliente passa por uma variedade de sistemas e subsistemas. Se você observar com cuidado, há vários dados de hops que precisam ser levados da origem para o escoamento, sendo que cada um deles é um possível candidato ao armazenamento em cache.

Fluxo de dados de um aplicativo CMS típico

Fluxo de dados de um aplicativo CMS típico

Vamos começar nossa jornada com dados que estão em um disco rígido e precisam ser exibidos em um navegador.

Hardware e sistema operacional

Primeiro, a unidade de disco rígido (HDD) em si tem algum cache integrado no hardware. Em segundo lugar, o sistema operacional que monta o disco rígido, usa a memória livre para armazenar em cache os blocos acessados com frequência para acelerar o acesso.

Repositório de conteúdo

O próximo nível é o CRX ou Oak - o banco de dados de documentos usado pelo AEM. O CRX e o Oak dividem os dados em segmentos que podem ser armazenados em cache na memória, bem como para evitar acesso mais lento ao HDD.

Dados de terceiros

A maioria das instalações maiores da Web também tem dados de terceiros; dados provenientes de um sistema de informações de produtos, um sistema de gerenciamento de relações com o cliente, um banco de dados herdado ou qualquer outro serviço da Web arbitrário. Esses dados não precisam ser obtidos da origem sempre que necessário - especialmente quando se sabe que não precisam ser alterados com muita frequência. Portanto, ele pode ser armazenado em cache se não estiver sincronizado no banco de dados do CRX.

Camada de negócios - aplicativo/modelo

Normalmente, os scripts de modelo não renderizam o conteúdo bruto proveniente do CRX por meio da API JCR. Provavelmente, você tem uma camada comercial no meio que mescla, calcula e/ou transforma dados em um objeto de domínio comercial. Adivinha só: se essas operações forem caras, considere armazená-las em cache.

Fragmentos de marcação

O modelo agora é a base para a renderização da marcação de um componente. Por que não armazenar o modelo renderizado em cache também?

Dispatcher, CDN e outros proxies

Off (Desativado) vai a HTML-Page renderizada para o Dispatcher. Já discutimos, que o principal objetivo do Dispatcher é armazenar em cache páginas HTML e outros recursos da Web (apesar do nome). Antes que os recursos cheguem ao navegador, ele pode passar um proxy reverso, que pode armazenar em cache e um CDN, que também é usado para armazenamento em cache. O cliente pode ficar em um escritório, o que concede acesso à Web somente por meio de um proxy - e esse proxy pode decidir armazenar em cache também para salvar o tráfego.

Cache do navegador

Por último, mas não menos importante, o navegador também é armazenado em cache. Esse é um ativo fácil de ser deixado de lado. Mas é o cache mais próximo e mais rápido que você tem na cadeia de armazenamento em cache. Infelizmente, ela não é compartilhada entre usuários, mas ainda entre diferentes solicitações de um usuário.

Onde armazenar em cache e por quê

Essa é uma longa cadeia de caches em potencial. E todos enfrentamos problemas em que vimos conteúdo desatualizado. Mas levando em conta quantos estágios há, é um milagre que na maior parte do tempo esteja funcionando.

Mas onde nessa cadeia faz sentido armazenar em cache? No começo? No final? Em todos os lugares? Depende… e depende de um grande número de fatores. Mesmo dois recursos no mesmo site podem desejar uma resposta diferente para essa pergunta.

Para dar uma ideia aproximada de quais fatores você pode considerar,

Tempo de vida - Se os objetos tiverem um tempo de vida inerente curto (os dados de tráfego podem ter um tempo de vida menor do que os dados meteorológicos), talvez não valha a pena armazenar em cache.

Custo de Produção - Quão caro (em termos de ciclos de CPU e E/S) é a reprodução e a entrega de um objeto. Se for barato, o armazenamento em cache pode não ser necessário.

Tamanho - Objetos grandes exigem mais recursos para serem armazenados em cache. Isso poderia ser um fator limitante e deve ser ponderado em relação aos benefícios.

Frequência de acesso - Se os objetos forem acessados raramente, o armazenamento em cache poderá não ser efetivo. Eles simplesmente ficariam obsoletos ou seriam invalidados antes de serem acessados pela segunda vez do cache. Esses itens apenas bloqueariam os recursos de memória.

Acesso compartilhado - Os dados usados por mais de uma entidade devem ser armazenados em cache mais acima da cadeia. Na verdade, a cadeia de armazenamento em cache não é uma cadeia, mas uma árvore. Um pedaço de dados no repositório pode ser usado por mais de um modelo. Esses modelos, por sua vez, podem ser usados por mais de um script de renderização para gerar fragmentos de HTML. Esses fragmentos são incluídos em várias páginas que são distribuídas a vários usuários com seus caches privados no navegador. Então "compartilhar" não significa compartilhar apenas entre pessoas, mas entre softwares. Se você quiser encontrar um cache potencial "compartilhado", basta rastrear a árvore até a raiz e encontrar um ancestral comum; é aqui que você deve armazenar em cache.

Distribuição geoespacial - Se seus usuários estiverem distribuídos pelo mundo, usar uma rede distribuída de caches pode ajudar a reduzir a latência.

Largura de banda e latência da rede - Falando em latência, quem são seus clientes e que tipo de rede eles estão usando? Talvez seus clientes sejam clientes móveis em um país subdesenvolvido usando conexão 3G de smartphones de gerações mais antigas? Considere a criação de objetos menores e armazene-os em cache nos caches do navegador.

Essa lista de longe não é abrangente, mas achamos que vocês já entenderam a ideia.

Regras básicas para cache encadeado

Novamente - o armazenamento em cache é difícil. Vamos compartilhar algumas regras básicas que extraímos de projetos anteriores que podem ajudá-lo a evitar problemas em seu projeto.

Evite o armazenamento em cache duplo

Cada uma das camadas introduzidas no último capítulo fornece algum valor na cadeia de armazenamento em cache. Economizando ciclos de computação ou aproximando os dados do consumidor. Não é errado armazenar em cache dados em vários estágios da cadeia, mas você deve sempre considerar quais são os benefícios e os custos do próximo estágio. O armazenamento em cache de uma página inteira no sistema Publish geralmente não fornece nenhum benefício, pois isso já é feito no Dispatcher.

Misturar estratégias de invalidação

Existem três estratégias básicas de invalidação:

  • TTL, Time to Live: Um objeto expira após um período fixo (por exemplo, "daqui a 2 horas")
  • Data de Expiração: O objeto expira em uma hora definida no futuro (por exemplo, "17h de 10 de junho de 2019")
  • Baseado em evento: o objeto é invalidado explicitamente por um evento que ocorreu na plataforma (por exemplo, quando uma página é alterada e ativada)

Agora, você pode usar diferentes estratégias em diferentes camadas de cache, mas há algumas "tóxicas".

Invalidação baseada em eventos

Invalidação baseada em evento puro

Invalidação com base em Evento Puro: Invalidar do cache interno para a camada externa

A invalidação pura baseada em eventos é a mais fácil de compreender, a mais fácil de se obter teoricamente correta e a mais precisa.

Simplificando, os caches são invalidados um por um após a alteração do objeto.

Você só precisa ter uma regra em mente:

Sempre invalide do cache interno para o externo. Se você invalidar um cache externo primeiro, ele poderá rearmazenar em cache o conteúdo obsoleto de um cache interno. Não faça suposições em que momento um cache está atualizado novamente; verifique se está. Melhor, disparando a invalidação do cache externo após invalidando o cache interno.

Essa é a teoria. Mas, na prática, existem várias armadilhas. Os eventos devem ser distribuídos - possivelmente em uma rede. Na prática, isto torna o regime de invalidação mais difícil de implementar.

Auto - Recuperação

Com a invalidação baseada em eventos, você deve ter um plano de contingência. E se um evento de invalidação for perdido? Uma estratégia simples pode ser invalidar ou limpar após um determinado período. Portanto, talvez você tenha perdido esse evento e agora forneça conteúdo obsoleto. Mas seus objetos também têm um TTL implícito de várias horas (dias) somente. Então eventualmente o sistema se autorrecupera.

Invalidação puramente baseada em TTL

Invalidação baseada em TTL não sincronizada

Invalidação baseada em TTL não sincronizada

Esse também é um esquema bastante comum. Você empilha várias camadas de caches, cada uma com direito a servir um objeto por um determinado período.

É fácil de implementar. Infelizmente, é difícil prever a duração de vida efetiva de um dado.

Perigo externo que prolonga o ciclo de vida de um objeto interno

Cache externo que prolonga a duração de um objeto interno

Considere a ilustração acima. Cada camada de armazenamento em cache apresenta um TTL de 2 minutos. Agora - o TTL geral deve 2 minutos também, certo? Não exatamente. Se a camada externa buscar o objeto pouco antes de se tornar obsoleto, ela realmente prolonga o tempo de vida efetivo do objeto. Nesse caso, o tempo de vida efetivo pode estar entre 2 e 4 minutos. Considere que você concordou com o departamento de negócios, um dia é tolerável - e você tem quatro camadas de caches. O TTL real em cada camada não deve exceder seis horas… aumentando a taxa de erro do cache…

Não estamos dizendo que é um mau esquema. Você só deveria saber os limites. E é uma estratégia legal e fácil de começar. Somente se o tráfego do site aumentar é possível considerar uma estratégia mais precisa.

Sincronizando o tempo de Invalidação ao definir uma data específica

Invalidação com base na data de expiração

Você obterá um tempo de vida efetivo mais previsível se estiver definindo uma data específica no objeto interno e propagando-a para os caches externos.

Sincronizando datas de expiração

Sincronizando datas de expiração

No entanto, nem todos os caches podem propagar as datas. E pode se tornar desagradável, quando o cache externo agrega dois objetos internos com datas de expiração diferentes.

Combinação de invalidação baseada em eventos e baseada em TTL

Combinando estratégias baseadas em eventos e em TTL

Combinando estratégias baseadas em eventos e em TTL

Um esquema comum no mundo do AEM também é usar a invalidação baseada em eventos nos caches internos (por exemplo, caches na memória em que os eventos podem ser processados em tempo quase real) e caches baseados em TTL na parte externa - onde talvez você não tenha acesso à invalidação explícita.

No mundo do AEM, você teria um cache na memória para objetos comerciais e fragmentos de HTML nos sistemas Publish, ou seja, invalidado, quando os recursos subjacentes mudam e você propaga esse evento de alteração para o dispatcher, que também funciona com base em eventos. À frente disso, você teria, por exemplo, um CDN com base em TTL.

Ter uma camada de cache (curto) baseado em TTL na frente de um Dispatcher poderia suavizar efetivamente um pico que normalmente ocorreria após uma invalidação automática.

Combinação de TTL e invalidação baseada em eventos

Combinando TTL - e Invalidação baseada em eventos

Tóxico: Misturar TTL - e Invalidação baseada em eventos

Esta combinação é tóxica. Nunca coloque um cache com base em eventos após um TTL ou Expiry. Lembra-se do efeito colateral que tivemos na estratégia "TTL puro"? O mesmo efeito pode ser observado aqui. Somente se o evento de invalidação do cache externo já tiver acontecido pode não ocorrer novamente - nunca, isso pode expandir a duração do objeto em cache para o infinito.

Combinação baseada em TTL e em evento: Transmissão para o infinito

Combinação baseada em TTL e em evento: Transmissão para o infinito

Cache parcial e cache na memória

Você pode conectar-se ao estágio do processo de renderização para adicionar camadas de cache. Desde a obtenção de objetos de transferência remota de dados ou a criação de objetos de negócios locais até o armazenamento em cache da marcação renderizada de um único componente. Deixaremos as implementações concretas para um tutorial posterior. Mas talvez você já tenha implementado algumas dessas camadas de armazenamento em cache por conta própria. Portanto, o mínimo que podemos fazer aqui é introduzir os princípios básicos - e as armadilhas.

Palavras de aviso

Respeitar o controle de acesso

As técnicas descritas aqui são bastante poderosas e um must-have na caixa de ferramentas de cada desenvolvedor de AEM. Mas não se empolgue muito, use-os sabiamente. Armazenar um objeto em um cache e compartilhá-lo com outros usuários em solicitações de acompanhamento realmente significa contornar o controle de acesso. Isso geralmente não é um problema em sites voltados ao público, mas pode ser, quando um usuário precisa fazer logon antes de obter acesso.

Considere armazenar uma marcação HTML do menu principal de sites em um cache de memória para compartilhá-la entre várias páginas. Na verdade, esse é um exemplo perfeito para armazenar HTML parcialmente renderizado, pois criar uma navegação geralmente é caro, pois requer percorrer muitas páginas.

Você não está compartilhando a mesma estrutura de menu entre todas as páginas, mas também com todos os usuários, o que a torna ainda mais eficiente. Mas aguarde… mas talvez existam alguns itens no menu que são reservados somente para um determinado grupo de usuários. Nesse caso, o armazenamento em cache pode se tornar um pouco mais complexo.