Como usamos Serverless para escalar nossos processos no RDStation

Publicado por Raphael Luiz Nascimento e Thiago Lu Silva no dia dev

Evolução Serverless

Neste post vamos contar como escalamos uma de nossas features utilizando uma arquitetura serverless dentro do RD Station Marketing, obtendo todos as vantagens que esta abordagem nos traz.

Antes de iniciarmos, vou apresentar a base do conceito sobre serverless, para que você não tenha dúvidas sobre isso.

Serverless

O termo surgiu em meados de 2012, Mike Roberts no seu artigo “serverless Architectures”, definiu:

“As arquiteturas serverless são aplicações que dependem significativamente de serviços de terceiros na nuvem (Baas) ou aplicações que possuem código que é executado em contêineres fora de sua aplicação (Faas)”

E aí, não entendi me explica melhor este negócio de Baas e Faas.

Baas

O termo Baas, refere-se à Background as a Service, imagine uma aplicação mobile onde quase 100% de seu funcionamento depende de serviços externos, tais como banco de dados na nuvem, serviços de autenticação, logs, etc. Muito empregado também em aplicações Single Page, nestas aplicações muitas das funcionalidades que deveriam ser codificadas são terceirizadas por estes serviços disponíveis na nuvem.

FaaS

Nada nos parece novo com relação a utilização de Baas, possuímos diversas aplicações utilizando serviços na nuvem, serviços de logging, monitoramento e autenticação são amplamente utilizados. Com FaaS, Function as a Service, o cenário muda, visto que aqui estamos indo mais além da utilização de um serviço na nuvem, estamos rodando o nosso próprio código na nuvem.

É um conceito novo que foi disponibilizado em 2014 pela hook.io e que agora é implementada pelos grandes players do mercado como AWS Lambda, Google Cloud Functions, IBM OpenWhisk e Microsoft Azure Functions.

Seu objetivo principal é prover para nós desenvolvedores a execução de código de forma rápida e escalável, tirando de nós a preocupação de construir ou manter uma infraestrutura complexa para isso.

Abaixo elenco umas das principais vantagens do uso do *FaaS

  • Escala inteligente, não escale sua aplicação, escale suas funções;
  • Não pague por servidor não utilizado, no idle time costs;
  • Foco no código, não se preocupe com infra;
  • Liberação modular da sua aplicação;
  • Alta disponibilidade e tolerância a falhas;
  • Time to market, prototipação e etc;

Também possui algumas desvantagens, por isso sua utilização sempre deverá ser ponderada.

Por exemplo, quanto mais funções você tiver mais complicada será para monitorar, publicar, versionar, etc. Outra desvantagem é com relação a utilização de dados que sempre precisam ser carregados, como dito acima cada função é executada em um container específico para aquela execução, os dados carregados numa execução anterior não estarão disponíveis na próxima execução, nesse caso tem que ser levada em consideração esta carga em cada chamada da função, o que faz necessária a utilização inteligente de mecanismos de cache.

Há bastante material disponível sobre o tema, porém, para o entendimento do nosso case, temos o suficiente até aqui.

E na Resultados Digitais?

Além de aplicações que se enquadram nas duas abordagens acima, também utilizamos serverless de uma outra forma que não está categorizada acima.

Como relatado acima, Faas é indicado para a execução de processos pequenos, que possuam um codebase relativamente baixo. Estamos falando na casa de 100~300 linhas de código, no máximo.

Nosso case possui quase todas características necessárias que direciona a nossa solução para uma arquitetura serverless utilizando Faas, porém, depois de algumas provas de conceito, vimos que não seria viável o uso de Faas. Continuamos utilizando uma arquitetura serverless, porém com uma nova tecnologia que ainda não está muito difundida; no caso, estamos usando um novo conceito de serverless.

A diferença principal é que, ao invés de executar uma função num servidor externo (faas), estamos executando uma aplicação completa dentro de um container de forma efêmera, como a Faas faz.

Ainda não há uma categorização para esta nova abordagem. Em poucos lugares que encontramos cases usando esta nova tecnologia, ela foi chamada de Serverless Docker Container, ou SDC.

Desta forma temos todas as vantagens do Faas, porém sem os requisitos de tempo de execução da função e tamanho do seu codebase.

O único ponto negativo é que o tempo de carga deste container Docker não é o mesmo do Faas, porém para nosso case isso não é considerado um problema.

Bora para o case.

Painel de Palavras Chaves

No RD Station Marketing temos uma funcionalidade chamada Painel de Palavras Chaves. Nela, podemos inserir palavras-chave relevantes para o negócio do cliente e acompanhar uma série de métricas relativas a estas palavras tais como relevância da palavra, preço, concorrência, entre outras informações.

Atualmente possuímos um database com um número relevante de palavras, sendo que o volume de dados aumenta significativamente a cada dia.

Com o aumento contínuo de clientes e, consequentemente, o número de palavras-chave crescendo de forma linear, estávamos chegando a cada dia próximo do limite do tempo de atualização das palavras, provisionando mais máquinas para o atendimento em certas ocasiões. Percebemos então a necessidade de reestruturar o processamento para uma arquitetura escalável.

Começamos nossos estudos com o mindset de pensar sempre em grandes escalas.

Será que esta nova arquitetura que estamos propondo irá aguentar a demanda para daqui a 5 ou 10 anos?

A partir deste questionamento partimos para o desenho da nossa solução.

Solução

Antes de partir para uma proposta colocamos como requisito macro o seguinte objetivo:

Como podemos processar uma quantidade X de palavras-chave por dia num tempo relativamente baixo, sendo que este tempo deve ser estável independente do número crescente de palavraschave?

A partir daí foram levantados alguns dos principais requisitos:

  • O processamento deverá ser transparentemente e escalável;
  • Financeiramente viável;
  • Não afetar/onerar o funcionamento do RD Station Marketing;
  • Monitoramento, Log, entre outros.

Proposta Inicial

Precisamos processar cada palavra diariamente e, para isso, é realizada uma requisição a um provedor externo que nos retorna as informações referente a esta palavra.

Inicialmente pensamos em utilizar o AWS Lambda para execução das funções como serviço (Faas). No caso cada palavra corresponde a uma função lambda que seria instanciada e seu processamento realizado.

Mesmo assim teríamos a necessidade de paralelizar a execução destas funções dentro do RD Station Marketing, visto que a execução sequencial destas palavras seria muito demorada.

Evitando que este paralelismo seja executado dentro da aplicação, chegamos a seguinte proposta inicial, como demonstrada na figura abaixo:

Proposta Inicial

Onde seu funcionamento seria da seguinte forma:

  1. Num horário pré-definido, um processamento assíncrono (rake) envia as palavras-chave para uma fila de entrada utilizando o aws sqs.
  2. Funções lambda configuradas com triggers que, ao identificar a chegada de uma nova mensagem na fila, automaticamente executa uma função lambda com o objetivo de extrair as informações retornadas pelo provider.
  3. O retorno destas funções são publicadas em um fila de saída que posteriormente será consumida por um outro processamento dentro do RD Station Marketing, somente para realizar funções de store desta informação na nossa base de dados.

Em nossa proposta nenhuma regra de negócio relativa ao processamento deve ficar dentro da aplicação. O processamento dentro do RD Station Marketing se limita apenas em enviar as palavras para a fila de entrada e consumi-las armazenando os novos dados dentro da nossa base de dados.

Através de provas de conceito, vimos que a função AWS lambda, ficaria muito inflada, ou seja, com uma quantidade alta de linhas de código, o que não é recomendável. A quantidade de regras de negócio, juntamente com a necessidade de utilização de bibliotecas externas, faria que o nosso codebase ficasse muito grande impactando na performance da execução da função.

Mesmo assim, vimos que a abordagem de utilizar uma arquitetura serverless fazia sentido para o nosso caso de uso. Chegamos então a uma nova proposta, esta sim nossa solução final.

Solução Final

Como dito acima, há outros requisitos como log, monitoramento, regras de negócio, entre outros. Para isso resolvemos escrever uma aplicação para realizar tudo que precisávamos.

Nossa aplicação deveria ser responsável por consumir a palavra da fila, realizar validações, buscar os dados no provedor externo, converter os dados e publicar o resultado na fila de saída.

Escrita em Ruby e utilizando Docker, nossa idéia era provisionar esta aplicação na nuvem para atender a nossa demanda atual e futura de maneira transparente e, de quebra manter esta estrutura somente ativa quando realmente ela fosse utilizada. Ou seja, ela deveria funcionar sob-demanda eliminando custos desnecessários de idle time. Chamamos esta aplicação de keyword-service.

Porém, antes de escrever a aplicação, vimos que a Amazon lançou o AWS FARGATE, solução que permite a execução de containers Docker sem a necessidade de gerenciamento de servidores. Foi o match perfeito! :-)

Vamos dar uma pausa no nosso case para explicar brevemente como o AWS Fargate funciona.

AWS FARGATE

Como mencionado acima, no que se refere a serverless na literatura, temos categorizados Baas e Faas. Porém, com o AWS Fargate temos agora a opção de executar containers em servidores serverless - mais precisamente containers shipados com Docker.

Com o AWS Fargate, podemos rodar nossa aplicação sem o gerenciamento habitual de servidores ou clusters como abordagem utilizada na execução da Amazon ECS. O foco fica somente na construção da aplicação, removendo qualquer preocupação com a infra necessária para a execução.

Como funciona

O AWS Fargate funciona baseado em task definitions. A partir desta definição o Fargate instancia e executa aplicação num container criado especificamente para essa execução.

Na task definition devemos colocar informações referentes ao consumo de recurso que a nossa aplicação precisará, tais como CPU, rede, memória, configurações de logging, etc.

Também é na task definition que são configurados os containers - no container que é definida a aplicação Docker que será executada. No nosso caso, nossa task ficou com três containers apontando para uma mesma aplicação Docker e sua respectiva versão.

Após a definição da task, seus containers e a publicação da imagem Docker da nossa aplicação no repositório da aws, nossa task está pronta para ser invocada.

Vamos mostrar como isso foi feito no nosso case.

Utilizando o Aws Fargate chegamos a seguinte solução final, como demonstrado na figura abaixo:

Solução Final

Como dito acima, o AWS Fargate se baseia em tasks definitions. Cada task deve possuir um ou mais containers. No nosso caso, configuramos cada task com três containers da nossa aplicação keyword-service.

Sendo assim, cada task por si só possui um grau de paralelismo igual a 3.

Também construímos uma aplicação Rails chamada keyword-manager para ser responsável por iniciar as tasks dentro do AWS. Para isso, utilizamos a API da AWS para realizar esta ação. Finalmente disponibilizamos esta ação através de um endpoint que será utilizado pelo RD Station Marketing.

Da mesma forma com a solução anterior, através de processamento assíncrono publicamos as palavras-chave numa fila na AWS. Porém, para evitar envios desnecessários de mensagens, cada mensagem enviada para a fila agrupa um total de mil palavras.

Ao final do envio das mensagens para a AWS é feita uma requisição para o endpoint acima mencionado passando o número de tasks que deverão ser iniciadas baseada na seguinte fórmula:

Fórmula

Onde:

  • Tasks: Número de tasks que serão provisionadas na AWS.
  • K: Número de palavras a ser processada no dia. Nossa base de palavras é distribuída em uma semana, ou seja, apenas uma palavra é processada uma única vez por semana. Então K se refere a carga diária de palavras que será processada.
  • KC: Número de palavras processadas por container dentro da task. Como cada task possui 3 containers, balanceamos para cada container um total de 10 mil palavras.

Podemos ver que nossa solução é flexível a ponto de decidir a quantidade de containers que serão levantados baseada na quantidade de palavras que serão processadas. Deste modo garantimos que o tempo de processamento relativamente igual independente do número crescente de palavras chaves.

Como o AWS SQS, garante a entrega de uma mensagem apenas a um consumidor. Temos a garantia que independente do número de containers estas mensagens serão processadas uma única vez.

Após o consumo de todas as mensagens, os containers são automaticamente suspensos e pagamos somente pelo tempo que eles processaram as mensagens.

Resultados

Com esta nova arquitetura, alcançamos os seguintes resultados:

  • Arquitetura escalável;
  • Redução do custo de alocação de máquina na nuvem;
  • Tempo de processamento indiferente da quantidade de palavras-chave;
  • Desenvolvimento focado apenas na aplicação;
  • Baixo acoplamento da solução com o RD Station Marketing;
  • Versionamento da aplicação automático pelo AWS Repository;
  • Logging e alertas no AWS Cloudwatch;
  • Evolução do serviço sem dependência do monolito da arquitetura.

Após a implantação desta nova arquitetura, através de gráficos de performance e tempo de execução do processo, garantimos que mesmo que a nossa base de palavras-chave cresça, o tempo de processamento continua sempre o mesmo. Isso sem mexer uma única linha de código ou provisionar mais recursos para isso.

Independente de uma boa arquitetura é necessário também que os custos sejam justificados junto aos stakeholders do projeto. Abaixo, segue uma ilustração dos nossos custos de um mês de desenvolvimento e teste, um custo total de incríveis US$ 1,81.

Custos

Com esta nova arquitetura, conseguimos eliminar um grande legado que era extremamente oneroso para o time, o qual gerava uma alta demanda de monitoramento, manutenção, procedimentos manuais e que não era escalável.

Com a utilização de uma arquitetura serverless alcançamos nossos objetivos trazendo a escalabilidade necessária para garantir a vida longa da nossa funcionalidade.

Raphael Luiz Nascimento

Raphael Luiz Nascimento

Software Engineer

Thiago Lu Silva

Thiago Lu Silva

Software Engineer

Comentários