Estruturando um System Design do zero para processar grandes volumes de dados, com AWS Lambda + Node.js - Parte II
No artigo anterior, abordei um desafio comum no processamento de grandes volumes de dados: a necessidade de reduzir drasticamente o tempo de execução ao lidar com arquivos enormes.
Utilizando AWS Lambda, conseguimos paralelizar o processamento, diminuindo o tempo de 17 minutos para 10 segundos. No entanto, esse foi apenas o início da solução.
Nesta segunda parte, vamos explorar dois outros componentes fundamentais para tornar a solução escalável e eficiente: o upload seguro de arquivos por fornecedores externos e o processamento desses arquivos sem sobrecarregar a memória.
Vamos começar discutindo como usar URLs assinadas da AWS (Signed URLs) para permitir que fornecedores externos subam arquivos diretamente para o S3 de forma segura. Isso resolve a questão de como receber dados externos sem comprometer a segurança ou precisar expor nossa infraestrutura diretamente.
Depois disso, veremos como processar esses arquivos grandes de forma eficiente dentro de uma AWS Lambda, utilizando streams do Node.js. Essa abordagem nos permite dividir arquivos massivos em blocos menores, que são enviados para filas SQS para serem consumidos pelo sistema descrito anteriormente. O resultado é uma solução otimizada para gerenciar arquivos enormes, mantendo o uso constante de memória, independente do tamanho do arquivo.
Essa combinação de URLs assinadas, processamento com streams e filas SQS é o que mantém o sistema robusto, escalável e eficiente. Ao final desta leitura, você terá uma visão clara de como montar esse pipeline completo de upload e processamento, pronto para ser escalado conforme as necessidades da aplicação.
AWS Signed URLs: Upload seguro por fornecedores externos
Um dos maiores desafios ao lidar com fornecedores externos é garantir que eles possam enviar arquivos diretamente para o seu sistema de forma segura e controlada. Em muitos casos, é preciso fornecer acesso ao armazenamento de arquivos (como o S3), mas sem expor a infraestrutura ou permitir que eles interajam diretamente com a API do seu sistema. É aí que entram as URLs assinadas da AWS (Signed URLs).
O problema: Controle de uploads externos
Como permitir que fornecedores enviem arquivos diretamente para o S3 sem comprometer a segurança? Sem o uso de URLs assinadas, seria necessário fornecer permissões permanentes de gravação para o bucket, o que é arriscado. Outra abordagem seria criar uma API para o upload, mas isso adicionaria complexidade desnecessária ao sistema.
A solução com Signed URLs
AWS Signed URLs resolvem esse problema de maneira simples e eficaz. Com elas, você gera URLs temporárias com permissões específicas para fazer o upload de arquivos diretamente no S3. A URL contém todas as permissões necessárias e expira após um período definido, garantindo que o fornecedor possa fazer o upload apenas dentro de um intervalo de tempo controlado.
Esse método não apenas mantém o sistema seguro, como também reduz a complexidade de implementação. O fornecedor apenas faz uma solicitação HTTP PUT para a URL assinada, e o arquivo vai direto para o bucket S3, sem que sua aplicação precise intermediar o processo.
Como funciona uma URL assinada?
Aqui está o fluxo básico:
Passos para gerar e usar Signed URLs
Aqui está um exemplo de como gerar uma URL assinada em Node.js usando o SDK da AWS:
Esse código gera uma URL assinada que permite fazer upload de um arquivo específico no bucket do S3. Note que o parâmetro Expires define o tempo de validade da URL, neste caso 5 minutos. Após esse tempo, a URL expira, impedindo que o fornecedor continue fazendo o upload.
Controle de permissões e segurança
Quando se utiliza Signed URLs, é importante garantir que as permissões no bucket S3 estejam corretamente configuradas. Por exemplo, o bucket pode ser configurado para bloquear uploads diretos, exceto quando feitos por uma URL assinada. Além disso, você pode limitar o tamanho do arquivo que pode ser enviado, adicionando uma camada extra de controle.
Boas práticas ao usar Signed URLs
Exemplo de implementação
Aqui está um fluxo de como a geração e o uso de URLs assinadas pode funcionar:
Essa abordagem oferece controle e segurança sem a necessidade de expor sua API diretamente aos fornecedores, além de manter o S3 como a única interface para uploads.
Processamento eficiente com Node.js Streams e AWS SQS
Após garantir que os fornecedores possam fazer o upload seguro de arquivos para o S3 utilizando URLs assinadas, o próximo desafio é processar esses arquivos de forma eficiente, principalmente quando lidamos com arquivos grandes que podem ocupar muita memória.
No nosso cenário, não basta simplesmente dividir o arquivo em blocos aleatórios: é fundamental que as transações presentes no arquivo sejam mantidas inteiras. Cada transação deve ser processada como uma unidade atômica, de forma que a API final possa trabalhar com lotes completos de transações. Se uma transação for fragmentada entre dois blocos, o processamento falhará.
A solução que vamos explorar aqui é usar streams do Node.js para ler o arquivo em blocos, agrupar as transações em lotes consistentes e enviar esses lotes para uma fila SQS, onde serão processados de forma paralela pelas Lambdas que descrevemos no artigo anterior.
O desafio da memória e o particionamento de arquivos
Lidar com arquivos de grandes proporções pode causar problemas de uso excessivo de memória em ambientes limitados, como o da AWS Lambda, onde o limite de memória disponível pode ser relativamente baixo (em torno de 100MB a 3GB). Carregar um arquivo de 1GB ou mais diretamente na memória seria inviável. Para contornar isso, usamos streams do Node.js.
Streams permitem que você leia e processe os dados de um arquivo em pequenos blocos, em vez de carregá-lo completamente na memória de uma vez. Essa técnica é crucial para processar grandes arquivos de maneira eficiente dentro de uma Lambda.
Como streams funcionam no Node.js
Streams no Node.js seguem um modelo baseado em leitura contínua e processamento de pedaços de dados, permitindo que você trabalhe com arquivos grandes de forma escalável e com baixo consumo de memória. Existem quatro tipos principais de streams:
No nosso caso, usamos um Readable Stream para ler o arquivo diretamente do S3 dentro da Lambda, processando-o em blocos menores, que são enviados para filas SQS para processamento posterior.
Agrupando dados de maneira lógica com Node.js Streams
Os streams do Node.js são ideais para processar grandes arquivos em pedaços menores, mas com um controle adicional: precisamos garantir que os dados sejam agrupados de forma que cada lote enviado à fila SQS contenha transações completas. Isso significa que o processo de leitura e agrupamento deve ser inteligente o suficiente para nunca enviar uma transação cortada.
O pipeline funciona assim:
Recomendados pelo LinkedIn
Exemplo prático de stream com agrupamento lógico em uma Lambda
Vamos ver um exemplo de como implementar isso dentro de uma Lambda, utilizando streams do Node.js para ler o arquivo do S3, agrupar as transações em lotes e enviá-los para o SQS.
Como esse fluxo funciona:
Benefícios dessa abordagem:
Escalabilidade: A fila SQS permite que os lotes sejam processados de forma paralela e escalável, com múltiplas Lambdas processando os dados simultaneamente.
Integração e Deploy com Serverless Framework
Agora que entendemos como o arquivo é processado em lotes de transações e enviado para o SQS, é hora de abordar como automatizar essa solução e facilitar o seu deploy. Para isso, o Serverless Framework é uma ferramenta extremamente útil, pois simplifica a configuração de Lambdas, filas SQS, buckets S3 e outros recursos da AWS, além de automatizar o deploy de todo o sistema com apenas alguns comandos.
Gatilho pelo evento de upload no S3
Uma das grandes vantagens da AWS Lambda é que ela pode ser disparada automaticamente por eventos de vários serviços AWS, como o S3. No nosso caso, toda vez que um fornecedor externo faz o upload de um arquivo no S3 usando a URL assinada, um evento é disparado, que ativa a Lambda responsável por ler o arquivo, processar as transações e enviar os lotes para o SQS.
Esse evento é configurado diretamente no bucket S3. Sempre que um novo objeto (arquivo) é criado no bucket, o S3 dispara o evento que ativa a Lambda.
Automatização com Serverless Framework
O Serverless Framework facilita muito a criação, configuração e deploy de recursos na AWS. Em vez de configurar cada serviço manualmente no console da AWS, você pode definir tudo em um arquivo serverless.yml, o que automatiza não só a criação das Lambdas, mas também dos buckets S3, filas SQS e qualquer outro recurso necessário.
Aqui está um exemplo de como configurar o Serverless Framework para criar toda a arquitetura discutida até agora:
Como essa configuração funciona:
Com essa configuração, você pode definir como os eventos no S3 disparam a Lambda de processamento de arquivos, e como essa Lambda, após processar e agrupar os dados em lotes, os envia para a fila SQS, que dispara outra Lambda para processar cada lote.
Deploy e gerenciamento fácil com Serverless Framework
Após configurar o serverless.yml, tudo o que você precisa fazer para criar e implantar toda a infraestrutura é rodar o comando:
Isso vai criar automaticamente as Lambdas, buckets S3, filas SQS e todos os outros recursos definidos no arquivo. Vantagens de usar o Serverless Framework
Com o Serverless Framework, todo o pipeline que criamos — desde o upload seguro no S3, até o processamento dos lotes e seu envio para o SQS — pode ser gerido de maneira muito mais eficiente. Você ganha agilidade tanto no deploy quanto na manutenção da solução, além de facilitar ajustes conforme o sistema cresce.
Design Geral da Solução
Com os conceitos e implementações discutidos ao longo deste artigo, temos agora um pipeline completo e eficiente para o processamento de grandes arquivos de forma escalável, segura e modular. Vamos revisar o design geral da solução e amarrar todos os componentes para que o fluxo fique claro.
Resumo do fluxo completo:
Processamento final dos lotes: As Lambdas consomem os lotes da fila SQS e os processam de acordo com a lógica descrita no primeiro artigo, garantindo que o sistema possa lidar com grandes volumes de dados de forma paralela e escalável.
Essa arquitetura é um exemplo claro de como a AWS, combinada com ferramentas como Serverless Framework e Node.js Streams, pode ser usada para criar sistemas eficientes e escaláveis. Cada componente é independente, mas trabalha em conjunto para garantir que os arquivos sejam processados rapidamente, sem sobrecarregar a infraestrutura ou comprometer a integridade dos dados.
Vantagens desse design:
O que vem a seguir: Observabilidade
No próximo artigo da série, vamos focar em como implementar técnicas de observabilidade para monitorar esse sistema em produção. Isso envolve configurar logs, métricas e alertas para garantir que possamos acompanhar o comportamento da solução, identificar gargalos e agir rapidamente em caso de falhas.