Como desenvolvedores, frequentemente enfrentamos desafios ao lidar com processamento e entrega de dados em grande escala. Na Kamero, recentemente resolvemos um gargalo significativo em nosso pipeline de entrega de arquivos. Nosso aplicativo permite aos usuários baixar milhares de arquivos associados a um evento específico como um único arquivo zip. Esse recurso, desenvolvido por uma função Lambda baseada em Node.js, responsável por buscar e compactar arquivos de buckets S3, enfrentava restrições de memória e longos tempos de execução à medida que nossa base de usuários crescia.
Esta postagem detalha nossa jornada de uma implementação Node.js que consome muitos recursos até uma solução Go enxuta e extremamente rápida que lida com eficiência com downloads massivos de S3. Exploraremos como otimizamos nosso sistema para fornecer aos usuários uma experiência perfeita ao solicitar um grande número de arquivos de eventos específicos, todos empacotados em um conveniente download zip único.
Nossa função Lambda original enfrentou vários problemas críticos ao processar grandes conjuntos de arquivos baseados em eventos:
Nossa implementação original usou a biblioteca s3-zip para criar arquivos zip a partir de objetos S3. Aqui está um trecho simplificado de como estávamos processando os arquivos:
const s3Zip = require("s3-zip"); // ... other code ... const body = s3Zip.archive( { bucket: bucketName }, eventId, files, entryData ); await uploadZipFile(Upload_Bucket, zipfileKey, body);
Embora essa abordagem tenha funcionado, ela carregou todos os arquivos na memória antes de criar o zip, levando a um alto uso de memória e possíveis erros de falta de memória para conjuntos de arquivos grandes.
Decidimos reescrever nossa função Lambda em Go, aproveitando sua eficiência e recursos de simultaneidade integrados. Os resultados foram surpreendentes:
Usamos o AWS SDK for Go v2, que oferece melhor desempenho e menor uso de memória em comparação com v1:
cfg, err := config.LoadDefaultConfig(context.TODO()) s3Client = s3.NewFromConfig(cfg)
As goroutines do Go nos permitiram processar vários arquivos simultaneamente:
var wg sync.WaitGroup sem := make(chan struct{}, 10) // Limit concurrent operations for _, photo := range photos { wg.Add(1) go func(photo Photo) { defer wg.Done() semEssa abordagem nos permite processar vários arquivos simultaneamente enquanto controlamos o nível de simultaneidade para evitar sobrecarregar o sistema.
3. Criação de Zip de streaming
Em vez de carregar todos os arquivos na memória, transmitimos o conteúdo zip diretamente para o S3:
pipeReader, pipeWriter := io.Pipe() go func() { zipWriter := zip.NewWriter(pipeWriter) // Add files to zip zipWriter.Close() pipeWriter.Close() }() // Upload streaming content to S3 uploader.Upload(ctx, &s3.PutObjectInput{ Bucket: &destBucket, Key: &zipFileKey, Body: pipeReader, })Essa abordagem de streaming reduz significativamente o uso de memória e nos permite lidar com conjuntos de arquivos muito maiores.
Os resultados
A reescrita para Go proporcionou melhorias impressionantes:
- Uso de memória: reduzido em 99% (de 10 GB para 100 MB)
- Velocidade de processamento: aumentada em aproximadamente 1000%
- Confiabilidade: lida com sucesso com 20.000 arquivos sem problemas
- Eficiência de custos: menor uso de memória e tempo de execução mais rápido resultam em custos reduzidos do AWS Lambda
Lições aprendidas
- A escolha do idioma é importante: a eficiência e o modelo de simultaneidade do Go fizeram uma enorme diferença em nosso caso de uso.
- Entenda seus gargalos: Criar o perfil de nossa função Node.js nos ajudou a identificar áreas-chave para melhoria.
- Aproveite as soluções nativas da nuvem: usar o AWS SDK for Go v2 e compreender os recursos do S3 permitiu melhor integração e desempenho.
- Pense em Streams: Processar dados como streams em vez de carregar tudo na memória é crucial para operações em grande escala.
Conclusão
Reescrever nossa função Lambda em Go não apenas resolveu nossos problemas imediatos de escalabilidade, mas também forneceu uma solução mais robusta e eficiente para nossas necessidades de processamento de arquivos. Embora o Node.js tenha nos servido bem inicialmente, essa experiência destacou a importância de escolher a ferramenta certa para o trabalho, especialmente ao lidar com tarefas que exigem muitos recursos em grande escala.
Lembre-se de que a melhor linguagem ou estrutura depende do seu caso de uso específico. Em nosso cenário, as características de desempenho do Go se alinharam perfeitamente às nossas necessidades, resultando em uma experiência de usuário significativamente melhorada e custos operacionais reduzidos.
Você enfrentou desafios semelhantes com funções sem servidor? Como você os superou? Adoraríamos ouvir sobre suas experiências nos comentários abaixo!
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3