Skip to content

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 dependencies e devDependencies no package.json
  • Ausência de configurações específicas no serverless.yml para 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

  1. Dependências de Monitoramento: Bibliotecas pesadas como o New Relic Agent estavam sendo empacotadas diretamente no código da aplicação
  2. Dependências de Desenvolvimento: Ferramentas de teste, lint e tipos TypeScript sendo incluídas no pacote de produção
  3. 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-processing utiliza 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:

  1. A compatibilidade entre as versões do Node.js no projeto e no Layer seja resolvida, OU
  2. 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:

  1. Identificar Dependências Não Utilizadas:
npx depcheck
  1. Analisar Tamanho das Dependências:
npx cost-of-modules
  1. Buscar Alternativas Mais Leves:

  2. Exemplo: substituir moment por date-fns (redução de ~200KB para ~70KB)

  3. Exemplo: substituir lodash por imports específicos lodash.get (redução de ~500KB para ~20KB)

  4. Atualizar Dependências:

  5. Manter dependências atualizadas pode trazer otimizações de tamanho
  6. 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:

  1. Gerenciamento de Dependências é Crítico: A diferença entre dependencies e devDependencies tem impacto direto e significativo no tamanho final do pacote. Uma organização incorreta pode facilmente dobrar o tamanho do deploy.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

7. Referências