Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Escalando um Monolito em Laravel

Escalando um Monolito em Laravel

Palestra apresentada no PHP Community Summit 2022

Mateus Guimarães

October 20, 2022
Tweet

More Decks by Mateus Guimarães

Other Decks in Programming

Transcript

  1. Escalabilidade de código Este termo existe? Será que podemos escrever

    código mais escalável? Nós sempre nos preocupamos com a escalabilidade em termos de infraestrutura, mas muitas vezes negligenciamos a qualidade e a habilidade do código de crescer — seja em termos de funcionalidades, modificações ou mesmo de processamento. Código bem escrito e testado = código mais fácil de modificar e crescer
  2. Entendendo o projeto Em 2019, eu trabalhei numa aplicação de

    gerenciamento e envio de campanhas por SMS. - Clientes enviavam listas de dados - Campanhas segmentadas eram criadas para cada uma dessas listas. - A plataforma cobrava um valor mensal.
  3. Módulos da aplicação Upload de listas 1. Número grande de

    dados (100M+) 2. Lookup de dados geográficos custoso 3. Processo demorado Criação de campanha 1. Queries relativamente custosas 2. Base de dados grande 3. Processos complexos durante a criação de cada mensagem a ser enviada Envio de mensagem 1. Rápido 2. Número alto de operações por segundo 3. Cálculo do número máximo de mensagens/minuto/conta durante tempo de execução 4. Suporte a diversos serviços de envio
  4. Módulos da aplicação Recebimento de respostas 1. Lógica simples mas

    custosa em grande escala 2. Verificações de "stop-words" no banco de dados 3. Possível bloqueio de envios para um número Envio de mensagens de resposta 1. Acontece após o recebimento de uma resposta positive (reply keyword) 2. Acontece como um envio normal de mensagem
  5. Stack utilizado • Laravel • MongoDB • Vue.js • Golang

    Grande parte da aplicação era escrita em Laravel. Para envio de mensagens, existia um worker escrito em Go que buscava mensagens pendentes no banco e as enviava.
  6. Problemas: upload de listas • Extremamente lento • Enfileirado, mas

    frequentemente timeouts aconteciam e o job era descartado
  7. Problemas: criação de contatos • Complexa e intensiva • Geração

    de dados de localização a partir do número de telefone custosa • Grande número de queries para cada contato • Número muito alto de contatos sendo criados por minuto
  8. Problemas: envio de mensagens • Número grande por segundo •

    Necessidade de verificar se esse contato já recebeu alguma mensagem do remetente nas últimas 24 horas
  9. Problemas: requisições Quando uma campanha começava a rodar, recebíamos dois

    tipos de webhooks: delivery reports e respostas em texto. O número de delivery reports era de 1 pra 1 — ou seja, para cada mensagem enviada, recebíamos ao menos uma requisição de volta. À medida que as campanhas eram iniciadas, o número de requisições por segundo aumentava e começávamos a ter problemas com o tempo de resposta e erros 500.
  10. Problemas: Mongo O Mongo funcionava muito bem até ter alguns

    milhões de registros em cada coleção. O fato de todas as operações de pausa/cancelamento de campanhas basearem-se em mover uma grande quantidade de dados de uma coleção para outra não ajudava.
  11. Problemas: Processamento de mensagens O programa em Go, que processava

    as mensagens pendentes, não tinha uma UI ou log, o que tornava o monitoramento complicado. Além disso, a adição de novos drivers era também complicada, já que era necessário adicionar código em dois lugares diferentes.
  12. A situação • ~100M contatos por dia • 1-2M mensagens

    enviadas/dia • Custo na casa dos 5 dígitos (em dólar) • Quedas constantes • Limpeza de dados diária
  13. Uma nova esperança Ficou decidido que eu faria uma nova

    versão, sozinho, na tentativa de corrigir os problemas que tínhamos. Para isso, utilizei: • Laravel • MySQL • Laravel Horizon • Redis
  14. Comece pelo básico É muito fácil pensarmos em todas essas

    ferramentas e esquecer do essencial, muitas vezes otimizando algo onde nunca houve um problema. • Seu banco de dados tem os índices corretos? • Seu banco tem os tipos corretos? (nada de VARCHAR(255))! • O seu servidor e gerenciador de processos (NGINX e PHP-FPM, por exemplo) estão corretamente configurados? • As suas queries estão otimizadas? • Você tem ferramentas para te informar onde estão os gargalos?
  15. Solução dos problemas de processamento Como tínhamos muitos processos intensivos

    e/ou em grande quantidade, a solução escolhida foi enfileirar tudo o que fosse possível e salvar em cache tudo o que conseguíssemos.
  16. Fatores importantes Em alta escala, é muito importante otimizar mesmo

    as pequenas coisas. Um exemplo disso é a forma como o Laravel lida com Models como argumentos nos jobs: ele desserializa o model durante o despacho, e serializa ele quando um worker consome o job. Nesse processo, o worker executa uma query para buscar esse model. Isso resulta em 2 coisas: - O estado que você espera nesse model talvez não seja o que o worker tenha - Uma query extra é executada, e fazer isso 1000 vezes por segundo pode ser problemático
  17. Resultados • 10-15M de mensagens enviadas por dia tranquilamente •

    300M+ contatos importados por dia • >500M jobs num período de 12 horas • > 30,000 reqs/min • Custo com infraestrutura abaixo de 900 USD • Facilmente escalável horizontalmente • Gerenciamento fácil das filas através do Laravel Horizon • Facilidade de adicionar novos provedores • Código fácil de modificar e estender, além de bem testado
  18. Um software sem testes não é de ninguém. Testes automatizados

    permitem à equipe ter mais segurança e conforto durante o desenvolvimento, e garantem a estabilidade da aplicação no longo prazo. Nunca conseguiríamos adicionar tantos provedores e garantir que tudo funcionava corretamente sem testes.