AWS API Gateway Privado: uma solução rápida para HTTP Cache

Eduardo Quagliato
Oct 15, 2020 · 7 min read

Como diminuímos em 90% a carga em nossa aplicação utilizando AWS API Gateway.

Nosso objetivo é empoderar os profissionais da saúde

Nosso objetivo aqui na Sanar é entregar conteúdo de qualidade e sempre atualizado para médicos e outros profissionais da saude. Esses conteúdos podem ser vídeos, questões de provas, provas na íntegra, comentários de especialistas etc.

Muito mais leitura que escrita

Por mais que esses conteúdos sejam gerenciados pelo nosso time operacional e pedagógico de maneira autônoma através de uma interface administrativa, a diferença na quantidade de alteração de conteúdo em relação à leitura desses conteúdos é gigantesca. Conteúdos são consultados na ordem de dezenas de milhares de vezes por dia e muitas vezes acima das centenas de milhares de vezes.

Resumindo: muito mais leitura que escrita. Um cenário mais do que propício para implementação de algum tipo de cache com ganho praticamente instantâneo de performance.

Cache?

“Tá, mas cache? Achei que isso fosse coisa de browser.”

Não é bem assim. Podemos descrever cache como uma ferramenta volátil de armazenamento de informações que é mais rápida de acessar do que a fonte de verdade, no nosso caso, um banco de dados MongoDB.

Como as coisas funcionam por aqui

Nossos vídeos são servidos através de uma CDN (Content Delivery Network), uma rede de servidores distribuídos ao redor do planeta, dessa maneira temos sempre uma versão do vídeo disponível o mais próximo possível (fisicamente) do aluno.

Já os “metadados” dos vídeos (por exemplo: duração do vídeo, título do vídeo, URL do vídeo etc.) são servidos por uma aplicação criada exclusivamente para servir nossos conteúdos.

Essa aplicação é auto-escalável e de acesso restrito apenas dentro de nossa VPC (Virtual Private Cloud), o que torna as possibilidades de implementação de cache mais simples e transparentes para nossos alunos. Como disse anteriormente, essa aplicação serve todos os nossos tipos de conteúdo: vídeos, questões de provas, provas na íntegra etc.

Mas se a nossa aplicação que serve conteúdos tem acesso restrito apenas dentro da VPC, como servimos isso para nosso front-end? Através de nossos BFFs.

BFF é uma sigla para Back-end For Front-end, uma aplicação de acesso público pela internet (mas autenticada) que, como o próprio nome já diz, funciona como um back-end para nossa aplicação front-end.

Então quando um aluno escolhe uma vídeo-aula para assistir, o caminho que a requisição faz é esse:

Primeira versão do cache

Inicialmente a solução projetada e implementada foi utilizar um gerenciamento ativo de cache baseado em Redis. E o responsável por esse gerenciamento ativo do cache era o nosso Serviço de Conteúdos.

O Redis é um banco de dados que armazena suas informações em memória, o que o torna muito mais rápido que SGDBs que persistem a informação em disco. E um dos usos mais comuns do Redis é como cache, através de uma estrutura de chave-valor, ou seja, para cada chave, existe um determinado valor armazenado.

Quando uma requisição para um determinado conteúdo era feita, o Serviço de Conteúdos procurava se a informação existia no Redis antes de procurar no MongoDB. Caso o conteúdo existisse no Redis, ele era retornado para o requisitante sem nem mesmo buscar no MongoDB, tornando a resposta muito mais rápida.

Na situação de o conteúdo não existir no Redis, buscávamos o conteúdo no MongoDB e gravávamos no Redis para que o próximo requisitante daquele mesmo conteúdo tivesse uma resposta mais rápida. No caso de alteração de algum conteúdo através da nossa interface administrativa, limpávamos a chave desse determinado conteúdo no Redis.

Essa é uma solução muito comum no mercado e muito utilizada em diversos sistemas. A princípio, ela funcionava muito bem pra gente, tendo sido implementada através de um plugin no Loopback (o framework utilizado no Serviço de Conteúdos).

Os problemas…

Por mais que utilizássemos uma versão gerenciada do Redis, em vários momentos tivemos problemas no gerenciamento de recursos do próprio Redis.

Quando, por exemplo, alterávamos uma árvore de conteúdos inteira de um curso ou quando adicionávamos um lote novo de conteúdos, o Redis perdia performance drasticamente e tornava a aplicação indisponível.

Sabíamos que nossa implementação possuía falhas que, à princípio, poderiam ser corrigidas para mantermos o uso do Redis como ferramenta principal do nosso mecanismo de cache para o serviço de conteúdo.

Mas tínhamos outras necessidades e objetivos quando o assunto era cache, então precisamos dar uma passo atrás e entender o que queríamos antes de decidir o que fazer.

Proposta para nova versão do cache

Tínhamos alguns objetivos em uma nova estrutura de cache:

  • Auto-gerenciada: não queríamos ter que cuidar ativamente dessa solução de cache e gastar esforço com manutenção;
  • Transparente para o Serviço de Conteúdos: gostaríamos que a aplicação funcionasse normalmente independente do serviço de cache e que o mesmo não precisasse ser acoplado à aplicação;
  • Rápida implementação: obviamente gostaríamos de gastar a menor quantidade possível de tempo/esforço nisso;
  • Que rodasse de maneira privada dentro da nossa VPC.

Como a comunicação entre nossos BFFs e o Serviço de Conteúdos acontece via HTTP (passando pelo Load Balancer), por que não utilizarmos um cache HTTP no meio desse caminho? Caches HTTP costumam ser fáceis de gerenciar, podem funcionar como proxy e então ser transparentes, além de a implementação seria rápida.

Mas o que é HTTP Cache?

Basicamente HTTP Cache é um cache que utiliza como chave uma determinada requisição (URI) e como valor o bodyrespondido naquela requisição. Então caso alguém fazer uma requisição com a mesma URI, o mesmo body pode ser respondido sem necessidade de processamento.

Pensamos em opções como nginx ou Varnish, mas queríamos algo que necessitasse da menor quantidade de manutenção possível. Gostaríamos também de utilizar serviços AWS, nosso provedor de escolha atual.

Em nossas buscas descobrimos que o AWS API Gateway possui uma opção para “cachear” seus endpoints. Também descobrimos que em 2018 a AWS lançou a funcionalidade de endpoints privados para o API Gateway.

É isso, testaríamos o API Gateway.

Implementação

Depois da PoC (Proof of Concept) que provou ser funcional essa ferramenta de cache, decidimos implementar uma alteração em nosso Serviço de Conteúdos que tornaria possível desabilitar o cache via Redis através de uma variável de ambiente.

Dessa maneira, fizemos o deploy de um segundo versão do Serviço de Conteúdos no ECS, sem o Redis. Então passamos a rodar simultaneamente as duas versões do Serviço de Conteúdos: uma com e outra sem o cache no Redis.

Mantivemos a versão com cache no Redis como a de uso principal para todos os BFFs e outros serviços da Sanar. Enquanto isso, implementamos o API Gateway com cache para dois endpoints específicos (de maior tráfego) e apontamos um de nosso BFFs para o API Gateway “cacheado”.

Resultados

Essa estrutura já funciona há pelo menos 2 meses e não tivemos problemas relacionados. Os números são bastante expressivos:

Os gráficos representam dados de uma semana e podemos ver que enquanto a quantidade de “miss” (quando a requisição não utiliza o cache) atingiu picos de aproximadamente 16 mil requisições, a quantidade de “hits” (quando a requisição utiliza o cache) atingiu picos de 130 mil requisições.

De maneira grosseira, podemos dizer que a cada 10 requisições, apenas 1 efetivamente vai ao banco de dados.

Nem tudo são flores

Mas nem tudo são flores. Algumas mudanças precisaram ser feitas para que a implementação funcionasse. E alguns comprometimentos também.

ALB x NLB

Nossa estrutura utiliza por padrão ALBs (Application Load Balancer) na frente de nosso serviços no ECS. Porém, o serviço de endpoints privados do API Gateway funciona apenas com NLBs (Network Load Balancer). Então tivemos que alterar nossas estruturas no Terraform para que fosse criado um NLB ao invés de um ALB para suprir essa necessidade.

Limpeza do cache

Na primeira implementação do nosso cache do Serviço de Conteúdos, como o gerenciamento do cache era ativo e feito pelo próprio serviço, quando um conteúdo era alterado, podíamos ativamente apagar do cache (Redis) os registros alterados e pronto. Super fácil.

Já no API Gateway, invalidar cache não é tão simples. Você precisa de uma policy específica para que um clientespecífico consiga invalidar determinado cache. Nada simples.

Nesse caso, adotamos o comprometimento de escolher um TTL (Time To Live, tempo em que a informação é armazenada no cache) de apenas 5 minutos, o que compromete um pouco nossos resultados mas garante que nossos alunos terão de esperar apenas 5 minutos caso um conteúdo seja esporadicamente alterado.

Query strings

Para que o API Gateway considere query strings na hora de montar a chave do cache, você precisa informar de maneira específica quais query strings serão aceitas. No nosso caso, como utilizamos o framework Loopback, que possui um conjunto bastante específico de query strings utilizadas em seus filtros, esse trabalho foi fácil. Mas se sua aplicação é bastante “aberta” com relação às query strings aceitas, isso pode ser um problema para você.

Gerenciamento de ambiente de staging e produção

A maneira mais comum de gerenciar ambientes no API Gateway é utilizando stages diferentes para cada um, o que permite variáveis diferentes para cada ambiente e assim por diante. O problema é que a estrutura de resources do API Gateway (os endpoints de verdade) é uma única para os dois stages.

Sim, é verdade que você pode fazer o deploy da estrutura de resources separadamente para cada stage, mas uma vez alterada, você está alterando para os dois. ☹️

E aí, vale a pena?

Obviamente a solução via Redis é muito mais robusta, mas também demanda manutenção muito mais rigorosa, o que pode ser um problema se você não possuir um time dedicado.

Então considerando os resultados, o tempo de implementação e o esforço de manutenção, eu diria que vale sim a pena. Mas apenas se você possuir informações que são raramente alteradas, como é o caso de nossos conteúdos.

sanar

Ajudando médicos a fazer a medicina melhor

sanar

Somos uma startup que atua em toda a jornada médica. Queremos ajudar médicos a fazer a medicina melhor. E você está convidado a entrar nesta jornada com a gente

Eduardo Quagliato

Written by

‘91 vintage 🇧🇷 SP/SP 🍕🥤🗣 comendo, bebendo e reclamando

sanar

Somos uma startup que atua em toda a jornada médica. Queremos ajudar médicos a fazer a medicina melhor. E você está convidado a entrar nesta jornada com a gente