Otimização de Tamanho de Pacotes Lambda
Status: Implementado
Última atualização: 09/10/2025
Criação: 09/10/2025
1. Resumo Executivo
Contexto
O repositório data-processing é um projeto serverless com múltiplas funções Lambda implementadas em Node.js. O projeto utiliza o Serverless Framework para gerenciar o deploy e a infraestrutura das funções na AWS.
Problema
Durante o processo de deploy das funções Lambda, o time encontrou o seguinte erro crítico de limite de tamanho da AWS:
Function code combined with layers exceeds the maximum allowed size of 262144000 bytes.
Este erro impedia completamente a publicação de novas versões das funções, bloqueando entregas e atualizações do sistema.
Objetivo
- Reduzir o tamanho final do pacote de deploy para viabilizar as publicações
- Melhorar a performance das funções (redução de cold start)
- Otimizar os custos operacionais do ambiente serverless
Resultado
O problema foi solucionado com sucesso através de uma série de otimizações no gerenciamento de dependências e uso de layers. As mudanças implementadas reduziram drasticamente o tamanho do pacote final, permitindo deploys normais e melhorando significativamente a performance geral das funções.
2. Diagnóstico e Análise Inicial
Identificação da Causa Raiz
A análise detalhada revelou que o tamanho excessivo do pacote era causado principalmente pela inclusão de dependências desnecessárias no artefato final de deploy. Especificamente:
- Dependências de monitoramento (New Relic) sendo empacotadas diretamente
- Dependências de desenvolvimento sendo incluídas no build de produção
- Má organização entre
dependenciesedevDependenciesnopackage.json - Ausência de configurações específicas no
serverless.ymlpara excluir pacotes não essenciais
Análise de Referência
O time consultou outros repositórios da organização que já haviam passado por um processo de otimização para o ambiente serverless. Essa análise foi crucial para identificar as melhores práticas a serem adotadas e evitar problemas já conhecidos.
Principais Focos de Otimização Identificados
- Dependências de Monitoramento: Bibliotecas pesadas como o New Relic Agent estavam sendo empacotadas diretamente no código da aplicação
- Dependências de Desenvolvimento: Ferramentas de teste, lint e tipos TypeScript sendo incluídas no pacote de produção
- Organização do package.json: Falta de separação clara entre dependências runtime e desenvolvimento
3. Ações de Otimização Implementadas
Esta seção detalha as soluções definitivas que foram implementadas para resolver o problema.
3.1. Utilização de Layer para o New Relic
Cenário Anterior:
A biblioteca do New Relic era uma dependência direta no package.json, adicionando um volume considerável ao node_modules (aproximadamente 30-40 MB).
Ação Realizada:
- A dependência NPM do New Relic foi completamente removida do
package.json - Foi adicionada a referência a um Lambda Layer oficial do New Relic, disponibilizado e mantido pelo time de plataforma
- O Layer contém toda a instrumentação necessária sem aumentar o tamanho do pacote da função
Implementação:
Adição da referência do Layer no arquivo serverless.yml:
functions:
processData:
handler: src/handlers/processData.handler
layers:
- arn:aws:lambda:us-east-1:451483290750:layer:NewRelicNodeJS20XARM64:72
Impacto:
- Redução significativa e imediata do tamanho do pacote
- Externalização de uma dependência pesada
- Facilita a atualização do agent de monitoramento sem necessidade de redeploy da função
3.2. Exclusão de Dependências de Desenvolvimento
Cenário Anterior:
O Serverless Framework, por padrão, pode incluir todas as dependências do node_modules se não for explicitamente instruído do contrário. Isso resultava na inclusão de ferramentas de desenvolvimento no pacote de produção.
Ação Realizada:
Foi adicionada a configuração excludeDevDependencies: true no serverless.yml para garantir que pacotes usados apenas para desenvolvimento (testes, lint, tipos, etc.) não fossem incluídos no artefato final.
Implementação:
package:
excludeDevDependencies: true
Impacto:
- Limpeza automática de todas as bibliotecas não essenciais para a execução da função em produção
- Redução estimada de 40-50% do tamanho do
node_modules - Exemplos de pacotes excluídos:
jest,eslint,@types/*,typescript,serverless-offline
3.3. Reorganização Estratégica de Dependências
Cenário Anterior:
Diversos pacotes que não eram necessários para a execução em tempo de runtime estavam incorretamente listados em dependencies, quando deveriam estar em devDependencies.
Ação Realizada:
Foi feita uma revisão criteriosa do arquivo package.json, movendo todas as bibliotecas que não são estritamente necessárias para a função rodar na AWS para a seção devDependencies.
Impacto: Esta foi a ação de maior impacto no projeto. Garantiu que apenas o código essencial fosse empacotado, resultando na maior redução de tamanho observada (redução superior a 90% do tamanho original).
4. Estratégias Avaliadas mas Não Implementadas
Documentar o que foi tentado e não funcionou (ou foi adiado) é tão importante quanto documentar o que deu certo.
4.1. Tentativa de Uso do ESBuild
Objetivo:
Utilizar o plugin serverless-esbuild para otimizar o empacotamento através de tree-shaking (eliminação de código não utilizado) e acelerar significativamente o processo de deploy.
Obstáculo Encontrado: Foi identificada uma incompatibilidade de versão do Node.js:
- O repositório
data-processingutiliza Node.js v22 - O Layer do New Relic adotado estava configurado e homologado para Node.js v20
- Esta divergência gerou conflitos de runtime que impediam a execução correta das funções
Decisão: A implementação do ESBuild foi adiada até que:
- A compatibilidade entre as versões do Node.js no projeto e no Layer seja resolvida, OU
- Um novo Layer do New Relic compatível com Node.js v22 seja disponibilizado
Observação: O ESBuild permanece como uma otimização altamente recomendada para implementação futura, com potencial de reduzir ainda mais o tamanho do pacote.
5. Recomendações e Próximas Etapas
Esta seção é um guia para o futuro, mostrando que o processo de otimização é contínuo.
5.1. Utilizar o ESBuild Corretamente
Sugestão: Assim que a questão de compatibilidade de versão do Node.js for resolvida, revisitar a implementação do ESBuild. Ele pode reduzir ainda mais o tamanho do pacote ao incluir apenas o código que é efetivamente utilizado (tree-shaking).
Benefícios Esperados:
- Eliminação automática de código não utilizado
- Build mais rápido
- Melhor performance de cold start
Exemplo de Configuração (serverless.yml):
esbuild:
bundle: true
minify: true
sourcemap: false
target: node22
platform: node
format: cjs
concurrency: 10
keepNames: true
mainFields:
- main
- module
resolveExtensions:
- .js
- .json
external:
- newrelic
- pg-native
- aws-sdk
packager: npm
define:
"require.resolve": undefined
logLevel: warning
5.2. Empacotamento Individual por Função (individually: true)
Sugestão:
Para uma otimização ainda mais granular, especialmente em repositórios com muitas funções com dependências distintas, utilizar a opção package: individually: true.
Como Funciona: Cada função Lambda é empacotada em seu próprio artefato ZIP, contendo apenas as dependências que ela realmente importa. Isso é particularmente útil quando:
- Diferentes funções usam diferentes subsets de dependências
- Algumas funções são "leves" e outras "pesadas"
- É necessário controle fino sobre o que cada função empacota
Desafio/Motivo para Não Adoção Atual:
A complexidade de mapear e declarar as dependências específicas para cada uma das muitas funções no repositório data-processing exigiria um esforço de refatoração considerável. No entanto, permanece como uma excelente opção para futuras otimizações, especialmente após a modularização do código.
Exemplo de Configuração (serverless.yml):
package:
individually: true
excludeDevDependencies: true
functions:
processOrders:
handler: src/orders/handler.main
package:
patterns:
- "src/orders/**"
- "src/shared/**"
- "!node_modules/**"
- "node_modules/axios/**"
- "node_modules/moment/**"
processPayments:
handler: src/payments/handler.main
package:
patterns:
- "src/payments/**"
- "src/shared/**"
- "!node_modules/**"
- "node_modules/axios/**"
- "node_modules/stripe/**"
generateReports:
handler: src/reports/handler.main
package:
patterns:
- "src/reports/**"
- "src/shared/**"
- "!node_modules/**"
- "node_modules/pdfkit/**"
Impacto Esperado:
- Redução no tamanho de funções específicas que não usam todas as dependências
- Deploys mais rápidos (apenas funções modificadas são reempacotadas)
- Cold starts mais rápidos para funções com pacotes menores
5.3. Auditoria Periódica de Dependências
Recomendação: Estabelecer um processo periódico (trimestral) de revisão de dependências para:
- Identificar Dependências Não Utilizadas:
npx depcheck
- Analisar Tamanho das Dependências:
npx cost-of-modules
-
Buscar Alternativas Mais Leves:
-
Exemplo: substituir
momentpordate-fns(redução de ~200KB para ~70KB) -
Exemplo: substituir
lodashpor imports específicoslodash.get(redução de ~500KB para ~20KB) -
Atualizar Dependências:
- Manter dependências atualizadas pode trazer otimizações de tamanho
- Versões mais novas frequentemente incluem tree-shaking melhorado
5.4. Considerar Dependências Externas
Sugestão: Para dependências muito grandes ou que mudam raramente, considerar o uso de Lambda Layers adicionais, além do New Relic já implementado.
Candidatos Potenciais:
- Bibliotecas de processamento de imagens
- SDKs de terceiros pesados
- Bibliotecas de machine learning
- Conjuntos de utilitários compartilhados entre múltiplas funções
6. Conclusão
Lições Aprendidas
O processo de otimização do tamanho dos pacotes Lambda no repositório data-processing revelou lições valiosas sobre o gerenciamento de dependências em ambientes serverless:
-
Gerenciamento de Dependências é Crítico: A diferença entre
dependenciesedevDependenciestem impacto direto e significativo no tamanho final do pacote. Uma organização incorreta pode facilmente dobrar o tamanho do deploy. -
Layers são Poderosos: A externalização de dependências pesadas através de Lambda Layers não apenas reduz o tamanho do pacote, mas também facilita atualizações e compartilhamento entre funções.
-
Configuração Explícita é Necessária: O Serverless Framework requer configurações explícitas (
excludeDevDependencies: true) para garantir comportamentos otimizados. As configurações padrão não são ideais para produção. -
Análise de Referência Acelera Soluções: Consultar repositórios similares que já passaram pelo processo de otimização economizou tempo significativo e evitou problemas já conhecidos.
-
Documentação é Essencial: Registrar não apenas as soluções implementadas, mas também as tentativas que não funcionaram, cria um histórico valioso para decisões futuras.
Resultado Final
A combinação de:
- ✅ Uso de Lambda Layers (New Relic)
- ✅ Exclusão automática de devDependencies
- ✅ Reorganização do package.json
Resultou em:
- ✅ Redução de mais de 90% no tamanho do pacote final
- ✅ Resolução completa do erro de limite de tamanho da AWS
- ✅ Melhoria de tempo de cold start
- ✅ Deploys mais rápidos
Recomendação Final
Para qualquer novo projeto serverless ou função Lambda em Node.js, implementar estas práticas desde o início evitará problemas futuros e garantirá uma arquitetura otimizada. A prevenção é sempre mais eficaz que a correção.
As próximas otimizações (ESBuild e empacotamento individual) devem ser consideradas conforme o projeto evolui e os requisitos de compatibilidade sejam resolvidos.