Olá a todos, Jules Martin aqui, de volta em agntmax.com!
Hoje quero falar sobre algo que me atormentou, e provavelmente atormentou muitos de vocês, no último ano ou mais: o aumento dos custos para a infraestrutura de nuvem, especialmente no que diz respeito às funções serverless. Todos nós fomos conquistados pelo sonho de “pague apenas pelo que usar”, e por muito tempo, parecia uma realidade. Mas ultimamente, vi as faturas aumentarem, às vezes inexplicavelmente, mesmo quando os padrões de tráfego parecem estáveis. É como se estivessem nos cortando pequenas despesas, graças à mesma flexibilidade que abraçamos. Portanto, vamos explorar algo muito específico e atual: Domando o Monstro Serverless: Desmascarando e Reduzindo os Custos Ocultos do AWS Lambda.
Minha jornada nisso começou há cerca de seis meses. Temos um microserviço central que gerencia a autenticação dos usuários e o gerenciamento de sessões. Está quase totalmente construído em AWS Lambda, API Gateway, DynamoDB e Cognito. Por muito tempo, os custos eram perfeitamente previsíveis. Então, no verão passado, nossa fatura da AWS para esse serviço específico aumentou cerca de 15%. Nenhuma nova funcionalidade, nenhum pico de tráfego significativo. Inicialmente, atribuí isso a alguma flutuação sazonal ou a um pequeno bug que ainda não havia encontrado. Mas quando a fatura do mês seguinte chegou ainda mais alta, sabia que precisava investigar. Não se tratava apenas de um pico; era uma tendência, e estava nos custando dinheiro de verdade.
A Ilusão dos Níveis “Gratuitos” e a Realidade das Invocações “Mínimas”
Um dos principais atrativos das soluções serverless, especialmente para startups ou equipes menores, é o generoso nível gratuito. E é realmente generoso! Um milhão de invocações gratuitas por mês para Lambda, além de uma quantidade significativa de tempo de computação. O problema é que, à medida que sua aplicação cresce, aquelas invocações “gratuitas” desaparecem mais rápido do que uma fatia de pizza em um meetup tecnológico. O que muitas vezes é negligenciado é o enorme volume de invocações mínimas, aparentemente insignificantes, que se acumulam. Pense nos cron jobs, nas verificações de saúde internas ou até nos mecanismos de reteração de outros serviços. Cada uma delas conta.
Minha investigação sobre nosso serviço de autenticação revelou exatamente isso. Tínhamos uma função Lambda, vamos chamá-la de auth-token-refresher, projetada para atualizar periodicamente os tokens de serviço internos. Estava programada para ser executada a cada cinco minutos. Parece inofensivo, certo? 288 invocações por dia. Multiplique isso por 30 dias, e você obtém 8.640 invocações por mês. Adicione nossos ambientes de desenvolvimento, staging e produção, e de repente há mais de 25.000 invocações apenas para uma pequena tarefa de manutenção. Tínhamos uma dúzia de tais funções. De repente, nossas invocações “mínimas” não eram mais tão mínimas.
Encontrando os Culpados: As Métricas do CloudWatch são Seu Melhor Amigo
O primeiro passo para domar essa besta é saber onde seu dinheiro está indo. O AWS CloudWatch é indispensável aqui. Não se limite a olhar o painel de faturamento em alto nível; explore as métricas específicas para suas funções Lambda.
Veja no que me concentrei:
- Invocações: Esta é a métrica mais direta. Contadores de invocações elevados para funções que não gerenciam tráfego direto de usuários são bandeiras vermelhas imediatas.
- Duração: Quanto tempo dura cada invocação? Durações mais longas significam custos de computação mais elevados.
- Uso da Memória: Você está superdimensionando a memória para suas funções? Pague pelo que atribui, não pelo que usa.
- Frequência de Erros: Altas frequências de erros podem levar a reiterações, o que significa mais invocações e ciclos de computação desperdiçados.
Para o nosso auth-token-refresher, examinei sua métrica `Invocations`. Com certeza, estava funcionando maravilhosamente, a cada cinco minutos. A duração era mínima, apenas cerca de 50ms. Mas o volume total estava contribuindo para nosso custo total das invocações.
Exemplo Prático 1: Consolidar e Planejar de Forma Inteligente
A solução para auth-token-refresher e várias outras funções de manutenção semelhantes era surpreendentemente simples: consolidação. Em vez de ter funções Lambda individuais acionadas por eventos do CloudWatch (ou EventBridge atualmente) com programações separadas, criei uma única Lambda “Maintenance Runner”.
Esse “Maintenance Runner” é ativado por uma única regra de evento do CloudWatch, digamos, uma vez por hora. Dentro deste runner, eu tenho um simples despachante que verifica a hora atual e executa as tarefas necessárias. Por exemplo:
import os
import datetime
def lambda_handler(event, context):
current_hour = datetime.datetime.now().hour
current_minute = datetime.datetime.now().minute
# Tarefa 1: Atualiza o token de autenticação (executava a cada 5 minutos)
if current_minute % 10 == 0: # Executa a cada 10 minutos agora
print("Executando atualização do token de autenticação...")
# Chama a lógica real de atualização do token ou outra função interna
refresh_auth_token()
# Tarefa 2: Limpa os logs antigos (executava a cada hora)
if current_hour % 1 == 0 and current_minute == 0: # Executa no início da hora
print("Executando limpeza dos logs...")
cleanup_old_logs()
# Tarefa 3: Verifica o status do serviço externo (executava a cada 30 minutos)
if current_minute == 0 or current_minute == 30:
print("Verificando o status do serviço externo...")
check_external_service()
return {
'statusCode': 200,
'body': 'Tarefas de manutenção executadas.'
}
def refresh_auth_token():
# ... lógica real de atualização do token ...
pass
def cleanup_old_logs():
# ... lógica real de limpeza dos logs ...
pass
def check_external_service():
# ... lógica real de verificação do serviço externo ...
pass
Essa simples mudança imediatamente reduziu a contagem de invocações para essas tarefas de manutenção de centenas de milhares por mês para algumas milhares. As economias em custos foram tangíveis, não só nas invocações do Lambda, mas também na absorção associada dos logs do CloudWatch e nas chamadas ao API Gateway (se alguma delas estava exposta através do API Gateway).
A Armadilha do Sobredimensionamento de Memória
Esse é outro fator de custo sutil que muitas vezes é negligenciado. Quando você cria uma função Lambda, atribui uma certa quantidade de memória (por exemplo, 128MB, 256MB, 512MB). Você paga por essa memória atribuída, independentemente de quanto a sua função realmente utilize. Além disso, a potência da CPU escala proporcionalmente à alocação de memória. Portanto, se você alocar 1GB de memória para um simples script Python que precisa apenas de 128MB, não está apenas pagando demais pela memória; você também está pagando em excesso pelos ciclos da CPU dos quais não precisa.
Eu aprendi isso da maneira mais difícil com uma Lambda de processamento de dados que estava inicialmente configurada com 1GB de memória “só para segurança”. Quando examinei suas métricas do CloudWatch para uso de memória, ele permaneceu constantemente abaixo de 200MB, mesmo durante cargas de pico. Basicamente, estávamos pagando por 800MB de RAM não utilizada e o correspondente aumento da CPU.
Exemplo Prático 2: Otimizando a Atribuição de Memória com o Lambda Power Tuning
Estabelecer manualmente a configuração de memória ideal pode ser cansativo. Você precisa implantar, testar, monitorar, ajustar e repetir. Felizmente, existe uma ferramenta open-source fantástica chamada AWS Lambda Power Tuning (desenvolvida por Alex Casalboni na AWS) que torna esse processo fácil.
É uma aplicação serverless que ajuda você a visualizar e identificar a configuração ideal de memória para suas funções Lambda com base em custos e desempenho. Você a implanta na sua conta AWS e, em seguida, pode usá-la para testar suas funções.
Aqui está como funciona geralmente:
- Implante a ferramenta Power Tuning através do Serverless Application Repository ou SAM.
- Invoque uma máquina de estados (criada pela ferramenta) com o ARN da sua função Lambda e um payload.
- A máquina de estados invoca sua Lambda várias vezes com várias configurações de memória (por exemplo, 128MB, 256MB, 512MB, 1024MB, etc.).
- Analisa os logs de execução e fornece uma visualização que mostra os compromissos de custo e velocidade para cada configuração de memória.
Para minha Lambda de processamento de dados, ao executar o Power Tuner, ficou claro que 256MB era o ponto ideal para os custos, com uma degradação de desempenho negligenciável em comparação com 1GB. Imediatamente reduzimos a alocação de memória para 256MB, resultando em uma redução de 75% no custo de computação para essa função específica. Isso não foi um evento isolado; desde então, tornei prática padrão executar novas funções ou aquelas reavaliadas através dessa ferramenta.
Para usá-la, após a implantação, normalmente você iniciaria a máquina de estados com algo assim (ajustando o ARN e o payload):
aws stepfunctions start-execution \
--state-machine-arn "arn:aws:states:REGION:ACCOUNT_ID:stateMachine:powerTuningStateMachine" \
--input '{ "lambdaARN": "arn:aws:lambda:REGION:ACCOUNT_ID:function:YOUR_FUNCTION_NAME", "num": 100, "payload": {}, "parallel": 5 }'
O output fornece um gráfico claro, mostrando exatamente onde seus custos e a velocidade se cruzam para desempenho ideal. É uma mudança significativa para a otimização de custos.
Verbosity dos Logs e Inícios a Frio
Duас outras áreas que muitas vezes se infiltram são a verbosity dos logs e os inícios a frio. Os logs do CloudWatch não são gratuitos. Cada linha que sua função Lambda imprime é assimilada e armazenada, e você paga por isso. Embora um bom registro seja crucial para a depuração, um registro excessivo (por exemplo, imprimir objetos inteiros ou repetir mensagens de estado desnecessariamente) pode rapidamente aumentar sua fatura de logs do CloudWatch.
Encontrei algumas funções que registravam o corpo inteiro da solicitação HTTP a cada invocação. Embora útil para o desenvolvimento inicial, em produção isso era apenas ruído e custo. Um ajuste rápido para registrar apenas os metadados essenciais (ID da solicitação, código de status, endpoint) reduziu drasticamente nossa absorção de logs.
Os inícios a frio, embora não sejam um “custo” direto da mesma forma, influenciam a experiência do usuário e podem indiretamente levar a mais tentativas ou a durações de faturamento mais longas se sua função tiver que esperar por recursos. Embora a AWS tenha feito avanços significativos na redução dos tempos de início a frio, otimizar o tamanho do pacote da sua função e evitar lógicas de inicialização complexas fora do manipulador ainda pode fazer a diferença. Para funções críticas e sensíveis à latência, a concorrência fornecida é uma opção, mas tenha cuidado, pois você paga por essa concorrência alocada mesmo quando está inativa.
Exemplo Prático 3: Registro Inteligente e Variáveis Ambientais
Para o registro, a solução mais simples é muitas vezes a melhor. Use variáveis ambientais para controlar os níveis de log. Em Python, por exemplo, você pode fazer isso:
import os
import logging
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO').upper()
logging.basicConfig(level=LOG_LEVEL)
logger = logging.getLogger()
def lambda_handler(event, context):
logger.debug("Esta é uma mensagem de depuração, visível apenas se LOG_LEVEL for DEBUG")
logger.info("Processando evento: %s", event.get('request_id'))
try:
# ... lógica da função ...
logger.debug("Processamento concluído para request_id: %s", event.get('request_id'))
return {
'statusCode': 200,
'body': 'Sucesso'
}
except Exception as e:
logger.error("Erro no processamento de request_id %s: %s", event.get('request_id'), str(e), exc_info=True)
return {
'statusCode': 500,
'body': 'Erro'
}
Definindo LOG_LEVEL para INFO em produção e DEBUG em desenvolvimento/staging, você pode reduzir significativamente sua conta do CloudWatch Logs sem sacrificar a observabilidade quando necessário.
Outro truque é estar ciente do que é inicializado fora do manipulador. Qualquer código diretamente no escopo global da sua função Lambda será executado durante o início a frio. Se você tiver operações caras, como pool de conexões de banco de dados ou grandes importações de bibliotecas, considere adiá-las até que sejam realmente necessárias dentro do manipulador ou garanta que estejam eficientemente armazenadas em cache para invocações quentes posteriores.
Conclusões Úteis para Sua Cruzada Contra os Custos Serverless
Bem, cobrimos bastante. Aqui está um resumo dos passos práticos que você pode tomar imediatamente para começar a reduzir aqueles custos Lambda traiçoeiros:
“`html
- Monitora incessantemente: Não se limite a dar uma olhada na sua fatura total da AWS. Explore as métricas do CloudWatch para Invocações, Duração e Uso da Memória para cada função Lambda. Configure alarmes para picos imprevistos.
- Consolide os cron jobs: Se você tem muitas pequenas funções Lambda programadas, considere combiná-las em um único “Executor de Manutenção” que distribui as tarefas com base em um cronograma menos frequente. Isso reduz drasticamente a contagem de invocações.
- Otimize a alocação de memória: Utilize ferramentas como AWS Lambda Power Tuning para encontrar a configuração ótima de memória para suas funções. Não se limite a adivinhar e sobre-alocar. Lembre-se, mais memória significa mais CPU, e você paga por ambos.
- Verifique a verbosidade do logging: Implemente níveis de log guiados por variáveis de ambiente (por exemplo,
INFOpara produção,DEBUGpara desenvolvimento). Evite registrar o corpo inteiro da solicitação ou estados internos excessivos na produção. Sua fatura do CloudWatch Logs agradecerá. - Revise as funções não utilizadas: Audite periodicamente suas funções Lambda. Existem funções antigas, experimentais ou depreciadas ainda ativas e gerando custos? Exclua-as!
- Fique de olho no tamanho dos pacotes: Pacotes de distribuição menores significam inicializações a frio mais rápidas e menores custos de armazenamento. Inclua apenas as dependências necessárias.
- Compreenda seu modelo de preços: Leia novamente a página de preços do Lambda. Entenda como são cobradas as invocações, os GB-segundos e a transferência de dados. O conhecimento é poder, especialmente quando se trata do seu bolso.
Domar o monstro serverless não significa evitá-lo; significa ser inteligente e intencional no uso que fazemos dele. A flexibilidade e escalabilidade são inestimáveis, mas sem a vigilância adequada, aqueles “pequenos” custos podem se acumular em uma parte significativa do seu orçamento. Vá em frente, monitore, otimize e economize!
Isso é tudo por hoje. Me avise nos comentários se você tiver outras sugestões ou truques para otimizar os custos do Lambda!
“`
🕒 Published: