Preparação e números de tech da maior beauty week do Brasil.

Marcio Ribeiro
6 min readDec 2, 2020

--

Beauty Week 2020

A beleza na web e o grupo boticário passaram pela maior beauty week nos e-commerces este ano e aqui estão alguns números e algumas das lições que aprendemos nos preparando para esta semana.

Photo: Pieter Stam De Jonge/AFP/Ritzau Scanpix

Primeiro um panorama do cenário e os números de infraestrutura :

  • 4 Kubernetes clusters ( AWS EKS ).
  • 60+ micro serviços.
  • 476 K8s ec2 worker nodes.
  • 4.000+ pods.
  • 200+ deployments.
  • 15TB ram em uso pelos clusters kubernetes.
  • 900 cpu cores em uso pelos clusters kubernetes.
  • 250.000 requests por minuto em nosso api-gateway.
  • 197.000 requests por minuto em nosso storefront.
  • 1 elasticsearch cluster , 200 i3.2xlarge data nodes.
  • 13.000+ buscas ativas por minuto em nosso cluster de elasticsearch( roda em ec2 self managed ).
  • 25 Rds postgresql DBs.
  • 25 mongodb replica sets + 1 sharded cluster ( Todos ec2 self managed ).
  • 6.5 Milhões de execuções da nossa principal função lambda durante a sexta feira.
Picos repentinos de 100% de requisições sustentados.

Suportamos picos de 100% de tráfego em questões de poucos minutos sem grandes picos de tempo de resposta.
Alcançamos este resultado realizando stress testes, pre scale e uma série de otimizações nas aplicações e a nível de infra.

Neste artigo foco nos números e otimizações de infra.

A realização de stress tests + visibilidade é crucial para obter este resultado. Testamos e garantimos a estabilidade do ambiente com uma carga maior do que a prevista.

Utilizamos o Locust para stress testing do backend. Front-end utilizamos um parceiro.

Diversos ajustes de aplicações foram necessários, mas encontramos e ajustamos alguns números a nível de deployment no kubernetes também :

  • Aplicações nodeJS receberam 1.5GB de ram por cpu. O máximo reconhecido pela engine V8 via flag. Assim atingimos a estabilidade e capacidade de lidar com picos repentinos de tráfego sem problemas.
  • 1GB de headroom para todas as apis java + ajuste fino de escala vertical para liberar theads e memória para as jvms.

Todos estes valores foram encontrados durante os testes de carga !

Nosso api-gateway durante o pico. Tempo de resposta otimizado e estável.
Api de agregação, faz parte do core e observamos um tempo de resposta otimizado também.

Nosso backend core também suportou com tempos de respostas otimizados durante os picos.

Durante os testes descobrimos que o backend demorava mais que o ideal para escalar organicamente em alguns casos, em outros mesmo escalando olhando para consumo de CPU / RAM ainda não era o suficiente para obter o tempo de resposta ideal.

Para tal foi necessário um pre scale para um mínimo de 25 pods em cada uma das apps. Com este valor o tempo de resposta estabilizou conforme acima.

Para 2021 queremos escalar por RPM e tempo de resposta da JVM além de CPU / RAM utilizando o custom metrics no kubernetes , atualmente já utilizamos o JMX_EXPORTER para reportar o consumo de ram diretamente da JVM para o custom metrics / hpa do kubernetes.

Otimizar as instâncias ec2 do mongodb pode reduzir significativamente o tempo de espera por IO.

Ao otimizar as instâncias ec2 dos mongodbs e os volumes EBS conseguimos reduzir o tempo de espera da CPU por IO. Saímos de uma média de 2% do tempo de CPU para apenas 0.2% ao adequar o tamanho da instância.
Isso traduz em querys rápidas e apis com tempo de resposta ideal e estável durante picos.

Outro indicador de saúde dentro do mongoDB, page faults !

Outro indicador de saúde que precisa ficar o mais próximo de 0 sempre é o page faults. Um número elevado desta métrica indica que o mongoDB está lendo muitos dados diretamente do disco pois o dataset não está cabendo na memória.
O tempo de resposta aumenta assim e a saúde geral cai. A ideia é sempre manter próximo de 0. Um número alto de documentos escaneados por minuto pode indicar falta de índice.

Api checkout com tempo otimizado após os ajustes.

Ao mudar a instance family para uma acima ( xlarge -> 2xlarge ) os indicadores de saúde melhoraram.

Um último ajuste foi conferir IOPS nos discos destas instâncias. Se a soma de IOPS de leitura + escrita estivessem acima do que o disco possuia e a métrica de queue no disco seja para leitura ou pra escrita estivesse acima de 0, nós provisionamos para IO1 com o número adequado de IOPS e removemos este gargalo.

Tempo de uma query após otimizações a nível de dados ( index ).

Nos bancos relacionais postgresql atuamos em 2 frentes :

  1. Camada de dados, otimização de querys e índices.
  2. Otimização a nível de RDS ( parâmetros do parameter group ).

Com otimizações nessas 2 frentes conseguimos resultados expressivos como o acima onde uma query com tempo médio de resposta de 80ms baixou para apenas 3.5ms.

Isso garante a estabilidade destes bancos e apis que dependem deles durante alto tráfego. Os valores e ajustes são muito específicos de acordo com cada banco e realidade. Porém alguns dos parâmetros que ajustamos foram :

wal_buffer , work_mem, effective_cache_size, effective_io_concurrency, shared_buffers …

Um último ponto de atenção foi o pool de conexões com estes bancos relacionais como o ambiente é elástico e o pool acompanhava precisamos ajustar de acordo. Para 2021 queremos um pool estático que não acompanhe o número de pods.

Um número elevado de conexões pode sobrecarregar o banco ou simplesmente esgotarem e novos pods não conseguirem se conectar ao banco e falharem o seu health check.

Nosso elasticsearch tem uma alta carga de acessos, por isso 200 data nodes.

Utilizamos a família de instâncias I3 recomendadas pela elastic e pela aws para este tipo de workload que precisa de muito IOPS e sofre picos no mesmo.
A família I3 é custo eficiente em termos de IO.

Com discos EBS estes picos se tornam um gargalo e provisionar discos IO1 não é custo eficiente.

A configuração de shards e réplicas por index é bem delicada e depende de cada ambiente, index e frequência de acesso. Temos alguns índices de alto acesso com mais de 10GB de dados e para estes utilizamos a configuração de :

11 shards.

10 réplicas.

Removemos índices antigos ( versões ) que ainda existiam ( caso um rollback emergencial fosse necessário ), esta ação aliviou todos os data nodes do cluster em termos de consumo de cpu e load average, mesmo estes índices não recebendo requisições.

Tempo de resposta estável na api de busca, a única api que faz requisições para nosso elasticsearch.

Outro ponto que foi essencial para a escalabilidade e estabilidade da plataforma foi o cache de DNS implementado diretamente nas aplicações nodeJS no front-end. Chegamos a estourar o hard limit de 1024 pacotes por segundo por ENI dentro da aws antes desse cache ser implementado e requisições de resolução DNS falhavam dentro do cluster.

Saimos de 5.000 requisições por minuto de DNS no cluster de front-end para apenas 300.

Um ponto que já deve ter ficado claro é que sem medir não é possível melhorar e otimizar. É um princípio básico se você quer otimizar algo você precisa medir e ter como comparar o antes e depois. Sempre trabalhamos em cima de métricas e dados.

Com toda essa preparação e testes conseguimos passar por uma excelente semana de beauty week com o ambiente estável. Aprendemos algumas lições e anotamos alguns pontos que não conseguimos identificar durante os testes.

O time por trás dessa infraestrutura tem apenas 6 pessoas, conseguimos esse grau de eficiência trabalhando com muita automação e gestão automatizada de configurações.

--

--

Marcio Ribeiro
Marcio Ribeiro

No responses yet