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:

  1. Geração da URL: Quando sua aplicação recebe uma solicitação para upload, ela gera uma URL assinada com as credenciais AWS, especificando as permissões (como putObject) e o tempo de validade da URL.
  2. Envio ao fornecedor: A URL assinada é enviada ao fornecedor, que a utiliza para fazer o upload do arquivo diretamente no S3.
  3. Controle de permissões: A URL só permite a operação de upload no local e no tempo definidos, garantindo que o fornecedor não tenha acesso irrestrito ao S3.

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

  • Limitar o tempo de validade: Quanto menor o tempo de validade, menor a chance de uma URL ser usada indevidamente.
  • Verificar o conteúdo: Quando o arquivo for carregado, é sempre bom validar o conteúdo após o upload (por exemplo, checar o tamanho ou o tipo do arquivo).
  • Gerenciar chaves de acesso: As chaves AWS utilizadas para gerar as URLs assinadas devem ser gerenciadas com cuidado, garantindo que estejam protegidas e rotacionadas periodicamente.

Exemplo de implementação

Aqui está um fluxo de como a geração e o uso de URLs assinadas pode funcionar:

  1. O sistema recebe uma solicitação do fornecedor para fazer upload de um arquivo.
  2. Sua aplicação gera uma URL assinada com permissões específicas e tempo de expiração controlado.
  3. A URL assinada é enviada ao fornecedor, que faz o upload diretamente no S3.
  4. Após o upload, o sistema valida o arquivo (opcional, mas recomendado) e o pipeline de processamento é iniciado automaticamente com base no evento de novo arquivo no S3.

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:

  • Readable Streams: Permitem a leitura de dados em partes (ou chunks).
  • Writable Streams: Permitem gravar dados em partes.
  • Duplex Streams: Permitem tanto leitura quanto escrita.
  • Transform Streams: Podem modificar os dados durante o processo de leitura e escrita.

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:

  1. Leitura do arquivo com streams: Usamos um Readable Stream para ler o arquivo em blocos, o que permite processar pedaços do arquivo sem carregá-lo inteiramente na memória.
  2. Agrupamento por transação: Conforme os blocos são lidos, as transações são acumuladas até que tenhamos um lote completo. Se o fim de um bloco interrompe uma transação, a Lambda espera até receber o próximo bloco para completar a transação.
  3. Envio dos lotes para SQS: Quando um lote completo de transações é formado, ele é codificado e enviado para a fila SQS. Cada lote contém várias transações agrupadas de forma lógica, o que garante que a API final receba os dados no formato esperado.

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:

  1. Gatilho do S3: Quando um novo arquivo é enviado para o S3, um evento dispara a Lambda.
  2. Leitura do arquivo com streams: A Lambda lê o arquivo do S3 em pedaços pequenos usando Readable Streams.
  3. Agrupamento de transações: A Lambda acumula as transações completas até formar um lote que faça sentido para a API final. Se uma transação for interrompida no meio de um bloco, ela espera pelo próximo bloco para completar a transação.
  4. Envio do lote para SQS: Quando o lote de transações completas está pronto, ele é enviado para a fila SQS.
  5. Processamento posterior: Outras Lambdas consomem esses lotes da fila SQS e processam as transações em paralelo, como descrito no artigo anterior.

Benefícios dessa abordagem:

  • Agrupamento consistente: As transações são mantidas inteiras, sem o risco de serem fragmentadas entre blocos.
  • Uso eficiente de memória: Como os dados são processados em blocos menores, a Lambda não precisa carregar o arquivo inteiro na memória.

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:

  1. Provider: Define que estamos usando a AWS como provedora de nuvem e que as Lambdas serão executadas com Node.js.
  2. Funções Lambda: Configura duas funções principais:
  3. Recursos:

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

  • Automação: Com apenas um comando, você consegue criar e configurar toda a infraestrutura da AWS necessária para a solução.
  • Facilidade de ajuste: Se você precisar aumentar a memória ou alterar algum parâmetro das Lambdas, basta ajustar o arquivo serverless.yml e redeployar.
  • Gerenciamento simplificado: O Serverless Framework também permite monitorar logs e métricas, facilitando o acompanhamento do funcionamento do sistema.

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:

  1. Upload seguro via URLs assinadas: Fornecedores externos enviam grandes arquivos para o S3 utilizando URLs assinadas, garantindo que o upload seja seguro e controlado.
  2. Gatilho pelo evento do S3: Assim que um arquivo é carregado no bucket S3, um evento é disparado, ativando a função Lambda que inicia o processamento.
  3. Processamento eficiente com Node.js Streams: A Lambda lê o arquivo usando streams para garantir que ele seja processado em pedaços menores, sem sobrecarregar a memória. As transações são agrupadas logicamente em lotes que façam sentido para a API final, garantindo que nenhuma transação seja cortada no meio.
  4. Envio dos lotes para o SQS: Após o agrupamento das transações em lotes completos, cada lote é enviado para uma fila SQS, onde será processado paralelamente por outras Lambdas.

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:

  • Escalabilidade: A solução se adapta automaticamente ao volume de dados, com o SQS e as Lambdas funcionando de forma paralela para processar os lotes de forma eficiente.
  • Eficiência no uso de memória: O uso de streams em Node.js garante que a Lambda consiga lidar com arquivos grandes sem sobrecarregar a memória disponível.
  • Segurança: As URLs assinadas mantêm o controle sobre quem pode fazer o upload e por quanto tempo, sem comprometer o acesso ao S3.
  • Automação e simplicidade: O Serverless Framework facilita a configuração, deploy e gestão de toda a arquitetura.

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.

Entre para ver ou adicionar um comentário

Outros artigos de Antonio Junior

Outras pessoas também visualizaram

Conferir tópicos