Olá hackers! Um dos assuntos amplamente discutidos em arquitetura é a performance em APIs. Ela influencia diretamente na utilização de recursos de servidores, e consequentemente, no seu nível de escalabilidade.
Eu sempre comento por aí que desenhar a arquitetura de uma solução é uma arte. E por mais que existam diversos patterns e técnicas, um projeto real tem inúmeras variáveis, por isso, não é algo tão trivial quanto parece.
Nesse artigo, vou esclarecer um pouco mais esse tema e ensinar como ter mais performance em APIs RESTful.
Se você não leu os primeiros artigos, dá uma lida para ficar 100% atualizado:
O que é cache?
Cache é uma estrutura computacional de armazenamento focada em manter cópias de dados que são acessados com frequência.
O propósito do cache é acelerar a busca de dados que são muito utilizados e poupar a utilização de recursos de um servidor. Ou seja, a performance em APIs é diretamente relacionada ao cache.
Com o cache, você tem os seguintes benefícios na sua API:
- Redução da latência de rede
- Redução de carga de processamento dos servidores
- Otimização de tempo de resposta ao client
Os caches são divididos em duas grandes categorias:
Shared cache — Armazena respostas de servidores para reutilização entre diversos usuários.
Private cache — Armazena respostas de servidores para utilização de um usuário único.
Esses caches podem ser implementados através do browser, servidor proxy, gateway, CDN, proxy reverso, ou load balancer de servidores WEB.
Cache no client
Quem leu os meus últimos artigos dessa série, sabe que uma API RESTful utiliza o protocolo HTTP para comunicação entre cliente e servidor.
Dado esse cenário, o protocolo HTTP tem suas especificações (RFC) para utilização de políticas de cache.
Todas elas são especificadas no header de uma requisição, agora vamos entender quais são as políticas disponíveis.
OBS: Nesse artigo não vou citar a especificação do HTTP 1.0, porque os métodos dessa versão estão depreciados.
Notem que ao chegar na sétima requisição, então a API não deverá permitir mais requisições, e retornará o HTTP status code 429 – Too Many Requests.
Controle de cache
O header cache-control é recomendado para declaração de mecanismos de cache. As principais diretivas disponíveis são:
No caching
Com a diretiva “no-store”, você estará especificando para que nem na requisição do client, e nem na resposta do server seja utilizado cache. Em outras palavras, toda vez que uma requisição for feita, o client fará o download da resposta do server.
Cache-Control: no-store
Cache but revalidate
Com a diretiva “no-cache”, o browser irá enviar uma requisição ao servidor para validação desses dados.
A ideia aqui é economizar largura de banda. Porém, com a certeza de que aquele cache está atualizado, ou seja, caso o servidor responda ao browser que o cache está atualizado, então o browser traz os dados do cache, e não faz o download dos dados do servidor novamente.
Cache-Control: no-cache
Private and public caches
A diretiva “public” permite que os dados de resposta do servidor sejam armazenados em um cache compartilhado. Já a diretiva “private” permite que os dados de resposta do servidor sejam armazenados no browser para utilização de apenas um usuário.
Expiration
A diretiva “max-age” é utilizada para setar o máximo de tempo (segundos) em que os dados ficarão disponíveis em cache.
Essa diretiva diz implicitamente ao browser que ele pode armazenar a página em cache, mas deve validar novamente com o servidor se a duração máxima for excedida.
Cache-Control: max-age=30
Validation
A diretiva “must-revalidate” obriga que o browser sempre revalide os dados com o servidor antes de utilizar o cache. Ou seja, nesse caso, o cache continua sendo utilizado normalmente, entretando ele é revalidado a cada nova requisição.
Cache-Control: must-revalidate
Entity Tags
O header ETag é um dos mais importantes quando falamos de cache.
Ele é utilizado como resposta do server para especificar uma versão dos dados que estão sendo retornados.
Nesse sentido, ele serve como uma chave única consistente, e possibilita um fluxo inteligente de consumo dos dados em cache pelo client.
O fluxo fica da seguinte forma:
- O client faz uma requisição de GET
- O server envia os dados e uma chave de ETag, que é armazenada em um storage local do client
- Client faz uma requisição com o valor da ETag no header “If-None-Match”,
- O server valida se aquele dado foi modificado, e caso ele não tenha sofrido alterações, o server retorna o HTTP status code 304 — Not modified. Veja no desenho a seguir:
Cache no Server
No tópico anterior entendemos como são as instruções de cache a nível de client HTTP.
Portanto, entendemos como manipular o gerenciamento de cache via frontend, mas agora precisamos entender como isso funciona na camada de server.
Então vamos entender quais são as principais técnicas disponíveis para quem vai desenvolver e se preocupa com a performance em APIs RESTful.
Load Balancing
Em cenários que sua API tem uma quantidade de requisições alta para apenas um servidor, então você utiliza essa técnica para distribuir a capacidade de processamento das requisições e tráfego de rede. Com isso você torna sua API mais escalável e segura, pois se um servidor falhar, outros estarão funcionando.
O load balancing por sua vez não tem uma funcionalidade default de cache, mas tem uma funcionalidade de armazenamento de sessão.
Como sabemos, o HTTP é stateless, ou seja, não armazena a sessão, porém quando é utilizado um load balancing, para fazer a melhor distribuição das requisições, ele faz o armazenamento da sessão do client, e por isso você pode utilizar como uma espécie de cache.
Existem diversos algoritmos de load balancing, e as principais implementações são feitas através de configurações do próprio Web server, como: Nginx, Apache, IIS.
Proxy Reverso
Um proxy reverso é basicamente uma interface pública da sua API, ou seja, ele funciona como um intermediador de todas as requisições externas.
As principais vantagens de utilização de proxy reverso são:
Segurança — Funcionalidades para proteção de ataques DDoS e configurações de certificados SSL.
Performance — Funcionalidades nativas de compressão dos dados tráfegados e cache, pois como ele atua como intermediador das requisições, ele consegue tratar os dados de forma inteligente antes de retornar ao client. Veja na imagem a seguir a utilização de um proxy reverso com load balancer:
As implementações normalmente também são feitas através de configurações do próprio Web server, como: Nginx, Apache, IIS.
Gateway
Pense num proxy reverso com mais funcionalidades, pensou?
Então, esse é o Gateway. Ele tem todas as funcionalidades de um proxy reverso, e muito mais.
Na prática, além de ser a interface de acesso a sua API para a internet, o gateway normalmente traz funcionalidades interessantes como: roteamento, monitoramento, autenticação e autorização, transformação de dados, segurança avançada por políticas, etc.
Ou seja, ele é o padrão de arquitetura e a técnica de utilização de cache mais indicada para uma boa performance em API RESTful.
Nesse caso, ao invés de utilizar o web server, você utiliza plataformas de API Management, como o SDP API Management, por exemplo. Veja na imagem a seguir:
CDN
CDN é um conceito muito difundido no mundo Web, e quer dizer Content Delivery Network — Rede de Distribuição de Conteúdo.
Ela funciona como uma rede de servidores que mantem réplicas de conteúdos Web e fazem uma distribuição mais otimizada desses conteúdos.
A grande “sacada” desse modelo é que as réplicas são feitas em geolocalizações estratégicas. Por isso, a CDN consegue redirecionar as requisições do client para um servidor mais próximo, reduzindo significantemente a latência de rede.
Como a latência de rede é reduzida, o tempo de resposta da requisição também é reduzido. Portanto, é uma ótima alternativa para uma alta performance em APIs que tem consumo em diferentes geolocalizações.
Essa técnica pode ser utilizada para Web no geral, ou seja, não é restrita apenas para o mundo de APIs.
Os principais players de CDN são: Akamai e CloudFront.
Compressão de dados [Bônus]
O padrão REST permite diversos formatos de dados como XML, JSON, HTML, etc.
Em todos eles, é possível comprimir a mensagem, fazendo com que o servidor trafegue menos dados, e gerando uma melhoria de performance em APIs.
Para isso, é necessário a utilização de alguns headers na requisição:
Accept-Encoding
Como client o header da requisição ficaria da seguinte forma:
Accept-Encoding: gzip,compress
Sendo que o Gzip é o formato padrão de compressão de dados.
Content-Encoding
No cenário em que o servidor tem a implementação da compressão de dados, além de obviamente compactar os dados para retornar ao client de forma otimizada, ele também retorna um indicativo desse processo através do header Content-Encoding, ou seja, o client deve receber o seguinte resultado:
200 OK
Content-Type: text/html
Content-Encoding: gzip
Algumas considerações importantes:
Se o Accept-Encoding estiver preenchido de forma não reconhecida pelo servidor, ele deverá retornar o status code 406 — Not Acceptable.
Se for um formato conhecido, porém o servidor não tiver implementado, então ele deverá retornar o status code 415 — Unsupported Media Type.
Um fato interessante é que a maioria dos browsers automaticamente fazem a requisição utilizando essas técnicas de compressão de dados.
Alcançando a alta performance em APIs RESTful
É isso aí meus hackers, estamos finalizando mais um artigo sobre essa série de APIs RESTful.
Como perceberam, performance é um dos assuntos mais amplos em desenvolvimento de software, mas para não estender muito o artigo vou parar por aqui.
Se você tem interesse em saber mais sobre performance em APIs deixe um comentário nesse artigo. Caso tenham muitos interessados, farei um artigo focado em como diagnosticar problemas de performance através de ferramentas de APM (Application Performance Management), stress and load testing, e muito mais.
Essa série também está chegando ao fim, e o próximo e último artigo é uma lista completa de boas práticas em desenvolvimento de APIs.
Aproveite e confira agora todos os nossos posts sobre APIs!