Objetivos

Referências complementares

O tratamento à solicitação de uma página

Quando uma página é solicitada, o primeiro passo é determinar se a mesma está ou não configurada para utilizar o mecanismo de cache. Caso não esteja, a página será processada dinamicamente e servida pelo arquivo main.jsp(o que fica bem evidente na URL da página). No caso de utilização de cache, é servida a página cache, que tem por default a extensão HTM, podendo esta definição ser alterada no arquivo de configuração do Lumis Portal (lumisportalconfig.xml) através da tag <htmlFileExtension>.

Uma página cache pode assumir dois estados básicos em relação ao seu HTML gerado: atualizado e desatualizado.

O estado atualizado indica que a página em cache está com a versão mais atualizada de seus conteúdos; em outras palavras, desde sua geração não ocorreu nenhum evento no Portal que implicasse em modificação do HTML da página cache (por exemplo, alteração de um conteúdo que nela esteja publicado).

Já o estado desatualizado de uma página em cache indica que algum evento ocorreu no portal, tal como mudança de um conteúdo, adição de uma nova interface na página, etc, tornando o HTML gerado anteriormente, ‘desatualizado’.

Páginas cache desatualizadas necessitam ser atualizadas (novamente geradas para serem servidas estaticamente). O Lumis Portal utiliza um recurso chamado de prioridade do cache para representar, para cada página, seu grau de importância de geração de um novo cache.

Quando uma página é solicitada e seu status é ‘desatualizado’, a prioridade do cache é incrementada. Isso é importante para assegurar que a geração de uma página que está sendo solicitada seja antecipada em relação a outras que possam ter uma prioridade inicial mais alta. Desta forma, uma página, quando solicitada, ganha mais prioridade que as demais, sendo gerada antes do que seria caso não houvesse tal solicitação.

Páginas desatualizadas têm seu arquivo correspondente de cache (HTML) fisicamente removido, a menos que o ambiente esteja explicitamente configurado para não remover páginas desatualizadas, atribuindo o valor “false” à tag <deleteOutdated> no arquivo de configuração do Lumis Portal. Com este arquivo removido, qualquer acesso realizado a essa página antes de sua nova geração exigiria que a mesma fosse obtida dinamicamente.

Entretanto, o Lumis Portal permite que seja armazenado um segundo nível de cache para que este seja servido nestas situações, ao invés de se precisar obter o conteúdo da página dinamicamente. Esse segundo nível é chamado de Shadow Cache. Quando utilizamos o Shadow Cache, mesmo que uma página esteja desatualizada e ainda não tenha sido gerada, ela será servida em sua versão antiga. No momento em que o novo cache for gerado, o Shadow Cache também será atualizado; desta forma, havendo uma nova expiração do cache desta mesma página, uma versão mais recente estará disponível.

A grande vantagem da utilização do Shadow Cache é evitar acessos dinâmicos para obtenção de uma página no ambiente. Porém, como desvantagem, páginas serão servidas desatualizadas enquanto estiverem em processo de geração. É preciso avaliar os requisitos do ambiente para determinar se a utilização de Shadow Cache é interessante ou não; por default esta configuração vem desabilitada.

Em páginas desatualizadas, quando não se usa o shadow cache a solicitação é concluída quando o arquivo cache da página for novamente gerado e disponibilizado, ou um erro é lançado caso o timeout seja ultrapassado; neste caso, é enviada uma resposta de erro ao cliente. O valor desse timeout pode ser definido no arquivo de configuração do Lumis Portal (lumisportalconfig.xml) na tag <pageRequestTimeout>.

O fluxo do tratamento a uma solicitação de página está ilustrado a seguir:

imagem1.jpg

Servidor HTTP e páginas em cache

Uma otimização recomendada para a arquitetura de uma aplicação é colocar um HTTP server à frente do Lumis Portal; este seria responsável por servir conteúdos estáticos, cabendo ao Lumis apenas o conteúdo dinâmico.

Para esse arquitetura funcionar, é necessário que o HTTP server enxergue todo o conteúdo estático. O HTTP server é configurado para servir conteúdo de um determinado diretório que contenha todo o conteúdo estático do portal. Inicialmente, devem ser copiados todos os elementos estáticos para esse diretório, o mesmo a ser utilizado pelo Lumis para gerar seu conteúdo estático. Esta configuração pode ser efetuada configurando-se a tag <webRootPath> no arquivo lumisportalconfig.xml.

Outra forma de garantir que o Lumis Portal grave seus arquivos no diretório utilizado pelo HTTP Server é mediante a configuração de pastas públicas, a partir da interface administrativa (Módulos->Sincronização->Pastas Públicas). Para maiores detalhes sobre pastas públicas, consulte a documentação através da URL http://linx.lumis.com.br/linx/documentacao/java

Geração de páginas em cache

Para tratar a geração de páginas em cache, o Lumis Portal conta com um robusto mecanismo de processamento das páginas a serem geradas através de filas, respeitando para cada página um ciclo de geração bem definido. Sustentando esse mecanismo existem processos para tratamento da expiração de cache baseados em eventos, e os processos de geração propriamente ditos. A seguir analisaremos cada aspecto deste sistema.

Ciclo de geração de uma página

Quando uma página é configurada para utilização de cache, seu status inicial é desatualizado (Outdated), uma vez que ainda não há cache gerado para a mesma. No momento em que o gerador tenta gerar o cache da página, o status passa a ser ‘em geração’ (Generating). No caso da geração do cache com sucesso a página passa para o status de ‘atualizado’ (Updated).

Quando ocorre um erro na geração do cache (status ainda em ‘Generating’), o contador de erros é incrementado e a prioridade é decrementada. Ao atingir o número máximo de tentativas de gerar o cache, definido no lumisportalconfig.xml, o status se torna desatualizado com erros (Outdated with errors). A tag que define este número máximo de tentativas é a <maxErrorCount>.

Outra tag utilizada nesse mecanismo de geração é a <errorPriorityDecrement>, que indica o valor a ser decrementado da prioridade a cada vez que houver um erro. O contador de erros pode ser zerado manualmente pela interface administrativa, tornando o cache da página novamente passível de geração. No caso de geração bem sucedida do cache, o contador de erros é zerado.

A principal causa de erros na geração de páginas é o lançamento de exceptions em interfaces presentes nessas páginas. Esse tipo de erro pode ser identificado nos logs através da mensagem:

“Error generating page: URL_DA_PAGINA- interface instance exception detected”

Ao nos depararmos com problemas de geração em uma página específica, é importante que seja realizada uma investigação sobre a página dinâmica, analisando as instâncias de interface uma a uma e verificando se alguma delas retorna uma ‘exception’. Após ser efetuada a correção do problema, pode ser necessário realizar a limpeza dos erros da página para que ela volte a ser considerada pelo gerador, no Gerenciador de Páginas em Cache.

Outra causa comum de erros de geração em páginas que utilizam layout files é a existência de referências incorretas no arquivo de layout, por exemplo, referência a uma interface que não exista no layout. Nesses casos, uma forma de se identificar o problema é remover uma a uma todas as referências existentes no layout file, forçando a geração da página a cada tentativa, até que o problema seja identificado. É possível forçar a geração de uma página através do Gerenciador de páginas em cache, como veremos a seguir.

Enquanto o gerador está gerando o cache de uma página, pode acontecer um evento que a torne desatualizada mesmo antes da geração ser concluída. Neste caso o status é alterado para ‘em geração desatualizado’ (Generating Outdated).

imagem2.jpg

É importante que o sistema reduza a prioridade das páginas que apresentam erro na geração; desta forma, pode-se garantir que o sistema não fique eternamente tentando gerar páginas com erros e oferecer oportunidade de geração para outras páginas, sem erros mas que em princípio teriam menor prioridade.

Geração automática e sob demanda

Existem dois modos de funcionamento do gerador de cache:

Geração sob demanda significa que as páginas solicitadas terão seu cache gerado apenas quando esta solicitação for efetuada. Na geração automática, além de se gerar cache para as páginas durante a solicitação, há um processo em background gerando todas as páginas configuradas para utilizar cache e que estejam desatualizadas.

Com a geração automática, aumenta-se a probabilidade da página já ter sido gerada quando for solicitada. A seguir são apresentadas as possíveis configurações relacionadas à geração de cache de página no arquivo lumisportalconfig.xml, com seus respectivos valores default:

<htmlGeneration>
   <onDemandOnly>true</onDemandOnly>
   <intervalBetweenPageGenerations>0</intervalBetweenPageGenerations>
   <intervalAfterStabilization>60000</intervalAfterStabilization>
   <numberOfThreads>1</numberOfThreads>
   <numberOfThreadsForImmediateUseOnly>5</numberOfThreadsForImmediateUseOnly>
   <threadPriority>5</threadPriority>
   <connectTimeout>30000</connectTimeout>
   <pageRequestTimeout>50000</pageRequestTimeout>
   <frameworkUrl>http://localhost:8080/portal</frameworkUrl>
   <htmlCacheLogNavigation>1</htmlCacheLogNavigation>
   <htmlFileExtension>.htm</htmlFileExtension>
   <shtmlFileExtension>.shtml</shtmlFileExtension>
   <deleteOutdated>true</deleteOutdated>
   <maxErrorCount>5</maxErrorCount>
   <errorPriorityDecrement>500</errorPriorityDecrement>
   <shadowCache>
     <enabled>false</enabled>
   </shadowCache>
   <ssi>
     <sendRedirectOnPageNotFound>true</sendRedirectOnPageNotFound>
     <waitBeforeSendRedirect>500</waitBeforeSendRedirect>
   </ssi>
</htmlGeneration>

A descrição de cada uma dessas configurações pode ser obtida no help do Lumis Portal em Portal Administration – Configuração – Gerenciador de Cache – Cache de páginas – Outras configurações, na url http://linx.lumis.com.br/linx/documentacao/java/

Na geração sob demanda, uma página é gerada apenas quando o Lumis receber sua solicitação. Entretanto, é necessário ressaltar que, dependendo da configuração, podem ocorrer problemas ao utilizar a opção de geração de cache sob demanda.

Cenários que utilizam um servidor HTTP e a opção deleteOutdated=false (indicando que o cache não deve ser apagado quando a página for desatualizada), o servidor HTTP continuará a servir a página em cache (desatualizada). Isso ocorre porque o servidor HTTP não percebe que a pagina cache está desatualizada, uma vez que o cache é gerado somente com a presença de uma nova requisição. Para evitar este problema, é obrigatório o uso do modo de geração automática, garantindo que o servidor de cache será atualizado de forma consistente pelo Lumis Portal.

O mecanismo de geração (Page Cache Engine)

O mecanismo de cache de páginas é iniciado no deploy do war do Lumis. Um servlet carregado automaticamente no startup é responsável por iniciar os mecanismos de cache de página.

<servlet>
<servlet-name>PageCacheGenerator</servlet-name>
<servlet-class>lumis.portal.page.cache.PageCacheGeneratorStarter</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
 

O PageCacheGeneratorStarter tem por função iniciar as threads utilizadas no mecanismo de cache de páginas. Esta classe possui uma lista de generators e encapsula as threads responsáveis pela geração do cache; possui também uma thread de limpeza de cache (PageCacheCleaner).

São iniciados dois grupos de generators, um para uso imediato e outro para a geração automática. O primeiro grupo atende a geração de páginas solicitadas pelo cliente, ou seja, existe cliente aguardando a geração desses caches. O segundo grupo atende a geração de cache de páginas que ainda não tenham sido solicitadas, ou seja, não há cliente aguardando a geração imediata desses caches; em outras palavras, estes arquivos serão gerados para uso futuro.

A quantidade de generators criada é configurada no lumisportalconfig.xml utilizando as tags <numberOfThreads> e <numberOfThreadsForImmediateUseOnly>, para os generators de geração automática e para os de uso imediato, respectivamente.

Expiração de páginas em cache

PageCacheCleaner é a classe responsável pela limpeza das páginas em cache. Esta classe implementa a interface Runnable, ‘escutando’ e mantendo uma fila de eventos do tipo PageRenderDataChangedEvent, que assinalam mudanças em um determinado conjunto de páginas e indicando, portanto, que o cache precisa ser expirado. Este evento encapsula os IDs das páginas que devem ter o cache atualizado, assim como parâmetros e locale das páginas onde a alteração deve ser aplicada.

Para que uma página dessa lista tenha o seu cache expirado, ela deve atender a duas condições:

O primeiro critério é intuitivo: se o evento indica alguma alteração sobre o conteúdo de páginas de um idioma, não faz sentido limpar o cache de versões das páginas em outros idiomas.

Já o segundo critério é um pouco mais sutil. Os parâmetros presentes no evento normalmente identificam um conteúdo alterado. Tipicamente, em serviços de conteúdo, a alteração de um conteúdo gerará a necessidade de expiração do cache da página de detalhes específica desse conteúdo e de todas as páginas que não sejam específicas apenas a outro conteúdo, como as páginas de lista.

Assim, expirar o cache das páginas que possuam na sua lista de parâmetros os mesmos parâmetros e valores do evento significa basicamente expirar o cache das páginas de detalhes do conteúdo alterado, enquanto que expirar o cache das páginas que não recebam os parâmetros do evento significa expirar o cache de páginas que contenham interfaces de lista e lista rápida daquela instância de serviço.

Esse tratamento tem por objetivo evitar a expiração de páginas de detalhes de outros conteúdos, que tipicamente não deveriam ser limpas no momento da alteração de um determinado conteúdo. Maiores informações sobre esta funcionalidade podem ser encontradas no artigo disponível emhttp://linx.lumis.com.br/linx/bc/java/expiring-html-cache.htm

A limpeza do cache só é realizada após o commit da transação.

O evento que dispara a expiração de páginas

A limpeza de cache de páginas pode ser disparada pela detecção de alguns tipos de eventos que, diretamente ou não, indiquem que o conteúdo de um determinado conjunto de páginas foi alterado.

O evento base nesse caso é representado pela classe PageRenderDataChangedEvent, que representa diretamente alterações no conteúdo de um conjunto de classes. Outras classes representam eventos derivados deste, e também são consideradas para realizar a limpeza do cache de páginas. A hierarquia entre estes eventos encontra-se representada a seguir.

imagem3.jpg

ChannelRenderDataChangedEvent indica alteração no conteúdo de um conjunto de canais. A partir dele são calculadas as páginas contidas nesse conjunto, para limpeza.

ChannelRenderDataChangedEvent changedEvent = new ChannelRenderDataChangedEvent(sessionConfig, Collections.singleton(channelConfig), transaction);

ManagerFactory.getPortalEventManager().notifyObservers(changedEvent);

ServiceInterfaceInstanceRenderDataChangedEvent indica alteração no conteúdo de um conjunto de instâncias de interface. A partir dele são calculadas as páginas que contém essas instâncias de interface, para limpeza.

IPortalEvent event = new ServiceInterfaceInstanceRenderDataChangedEvent(sessionConfig, Collections.singleton(interfaceInstanceConfig), transaction);

ManagerFactory.getPortalEventManager().notifyObservers(event);

ServiceInstanceRenderDataChangedEvent indica alteração no conteúdo de um conjunto de instâncias de serviço. A partir dele são calculadas as páginas que contém instâncias de interface dessas instâncias de serviço, para limpeza.

IPortalEvent event = new ServiceInstanceRenderDataChangedEvent(sessionConfig, Collections.singleton(serviceInstanceConfig), transaction);

ManagerFactory.getPortalEventManager().notifyObservers(event);

ContentRenderDataChangedEvent indica alteração em um conteúdo específico. A partir dele são calculadas as páginas que possuem instâncias de interface que devem sofrer modificação por causa da alteração no conteúdo, para limpeza.

ContentRenderDataChangedEvent event = new ContentRenderDataChangedEvent(sessionConfig, contentVersion, (ContentTableSource)source, transaction);

event.setOperationType(PortalEventOperationType.UPDATE);

ManagerFactory.getPortalEventManager().notifyObservers(event);

O mecanismo que popula a fila de expiração de páginas

O mecanismo de cache se utiliza de um ‘observer’ representado pela classe PageCacheObserver, cuja definição no arquivo observersdefinition.xml está representada a seguir.

<observer>
<name>PageCache</name>
<class>lumis.portal.page.cache.PageCacheObserver</class>
</observer>
 

Este observer tem como função filtrar os eventos do tipo PageRenderDataChangedEvent e adicioná-los à fila de eventos da PageCacheCleaner para limpeza.

Gerenciador de páginas em cache

Todo o processo de geração de cache de página no Lumis Portal pode ser acompanhado através do Gerenciador de páginas em cache. Esta interface é acessada através do menu Configuração – Gerenciadores de Cache – Páginas em Cache, representada a seguir.

imagem4.jpg

Nessa interface é possível visualizar o status de todas as páginas em cache do Portal. Repare que se, por conta dos parâmetros recebidos, uma mesma página do Portal tiver diferentes caches de página gerados (por exemplo, no caso de uma páginas de detalhes de notícias), cada um desses arquivos será exibido como um registro nessa interface.

Para cada página gerada em HTML, é possível visualizar a data e hora de criação (ou data e hora em que seu cache foi gerado pela primeira vez), data e hora de última geração, data e hora da última expiração, prioridade atual da página, número de erros de geração detectados e status.

Essas informações podem ser valiosas na análise da saúde do ambiente no que diz respeito à geração de páginas em cache. Por exemplo, em cenários com gerador automático habilitado, podemos analisar os valores do campo “Expirado em” das páginas desatualizadas para avaliar o tempo médio necessário para que uma página seja gerada novamente.

Além disso, a partir do número total de páginas desatualizadas podemos acompanhar a fila de geração e verificar se a mesma está crescendo, diminuindo ou se mantendo estável, um sinal da capacidade do ambiente de manter o Portal atualizado frente ao ritmo de publicação.

Nesta interface também é possível forçar a geração de uma página imediatamente através do botão “Gerar”. Além disso, é possível desabilitar ou habilitar todo mecanismo de geração de páginas em cache do Portal. Quando este mecanismo estiver desabilitado, não serão geradas quaisquer páginas em cache, seja através do gerador automático ou através da geração sob demanda.

A limpeza de erros, disparada pelo botão “Limpar erros”, é útil para situações em que uma página já tenha atingido o limite do número de erros de geração configurado. Após a resolução do problema que estava impedindo sua geração, a forma de fazer com o gerador volte a considerar a página é através dalimpeza de seu contador de erros.

Arquitetura e desenvolvimento considerando cache de página

É recomendado que a utilização de cache seja levada em consideração desde o início do projeto.

Muitas vezes tenta-se implementar cache apenas ao final de um projeto, momento em que diversas restrições podem inviabilizar o bom uso desta funcionalidade. É interessante estar ciente de todas as restrições o quanto antes.

Para que uma página possa ser colocada em cache, é preciso que seja realizada uma avaliação sobre quais componentes da página são dinâmicos e variam de acordo com alguma regra: usuário autenticado, data e hora ou qualquer outro que faça com que uma mesma interface possa ser apresentada de formas diferentes.

Uma vez realizada essa avaliação, caso não existam componentes dinâmicos como os citados acima a página pode ser colocada em cache, já que ela só apresentará variação de conteúdo a partir de publicação e, nesse caso, o cache será expirado.

Caso a página apresente componentes dinâmicos, será preciso analisar a natureza desse dinamismo. Se estas variações de apresentação puderem ser tratadas no lado do cliente, de modo que o conteúdo HTML da página gerado em arquivo seja sempre o mesmo, a página também poderá ser colocada em cache.

Um exemplo desse tipo de variação seria uma interface que precisa exibir o nome do usuário autenticado, obtendo essa informação de um cookie. O código gerado para a página, server-side, é idêntico. A variação na apresentação se dá por conta de um javascript, executado client-side, que obtém o nome do usuário e exibe na tela.

Para o caso de interfaces que precisem de fato calcular dinamicamente e server-side o seu conteúdo a cada renderização, ainda existe a opção de configurá-la para renderização via client-side script. Esta configuração é feita na interface de propriedades de uma instância de interface, acessada através do item “Propriedades” no menu do right-click, ilustrada a seguir.

imagem5.jpg

Quando uma interface é configurada para renderização via client-side script, seu conteúdo final para exibição não é calculado server-side enquanto o código HTML da página está sendo gerado. Ao invés disso, no trecho correspondente a essa interface na página é gerada apenas uma chamada ajax para o servidor que obterá, client-side, o conteúdo final dessa interface.

Utilizando esse recurso, mesmo com interfaces dinâmicas o conteúdo HTML gerado para a página é o mesmo. Apenas no momento em que esse HTML for servido para o cliente a interface será populada, através de uma chamada dinâmica.

Na prática, esse recurso é muito útil no cenário típico em que temos uma página que seja inteiramente estática, com apenas uma interface dinâmica. Nesses casos, colocar a página em cache e configurar essa interface para ser renderizada via client-side script pode ser uma boa solução para a performance, de modo que o conteúdo da página não precisa ser inteiramente calculado a cada requisição e a página mantém sua característica de dinamismo.

Não há restrição de uso desse modelo para páginas que possuam apenas uma interface dinâmica, a configuração pode ser aplicada para quantas interfaces for necessário. A questão é que esse tipo de renderização, embora apresente benefícios, acaba sendo mais custosa se analisarmos apenas a renderização da interface pontualmente, já que além de todo processamento server-side (que será executado de qualquer forma) ainda estamos acrescentando a necessidade de mais uma requisição HTTP para a chamada ajax. O benefício é basicamente a mudança do momento em que esse processamento será realizado.

Sendo assim, se uma página possuir muitas interfaces dinâmicas que precisem ser configuradas para renderização client-side script para permitir a configuração da página em cache, essa abordagem pode acabar piorando o tempo de processamento necessário, sendo melhor manter a página dinâmica.

Outro mecanismo que viabiliza páginas a serem cacheadas é o Server Side Include (SSI). Quando uma parte da página é dinâmica pode-se usar o SSI para gerar um HTML apenas para essa parte, fazendo com que a página solicitada contenha apenas um ‘include’ deste arquivo em seu HTML.

Para configurar uma instância de interface para gerar SSI é necessário realizar a configuração no campo “Server Side Include” da mesma aba “Desempenho” na interface de propriedades da instância de interface.

imagem6.jpg

Este mecanismo é muito útil quando uma mesma interface é utilizada em várias páginas. No caso de atualização do conteúdo da interface, sem o SSI, várias páginas se tornariam desatualizadas , sendo portanto necessário ao sistema regerar várias páginas. Usando o SSI, quando há uma atualização da interface apenas o HTML desse trecho é expirado e portanto apenas esse HTML (que representa uma única interface sendo, portanto, mais leve do que o HTML da página completa) tem de ser gerado.

Por todas essas razões, é fundamental que a análise sobre utilização do cache seja realizada na fase de arquitetura, de modo que o desenvolvimento seja orientado para permitir que as interfaces das páginas que precisam estar em cache sejam desenvolvidas permitindo essa configuração e o cache seja definido da forma mais adequada a atender os requisitos da solução.

Postergar essa análise pode fazer com que não seja possível otimizar as configurações de cache devido a restrições dos serviços desenvolvidos, gerando a necessidade de retrabalho ou de adaptações quanto a definição de cache, eventualmente chegando a impedir a utilização de cache para o cenário em questão.

Principais pontos para abordagem prática

Autor: Pedro Torres (Lumis)