Olá a todos, Jules Martin aqui, de volta ao agntmax.com!
Hoje quero abordar algo que me incomoda, e provavelmente a muitos de vocês, há cerca de um ano: o custo crescente da infraestrutura em nuvem, especialmente no que diz respeito às funções serverless. Todos nós ficamos fascinados pelo sonho de “pagar apenas pelo que você usa”, e por muito tempo isso parecia uma realidade. Mas recentemente, vi as faturas aumentarem, às vezes de maneira inexplicável, mesmo quando os padrões de tráfego pareciam estáveis. É como se fôssemos corroídos pela mesma flexibilidade que havíamos adotado. Então, vamos explorar algo muito específico e atual: Dominando o Monstro Serverless: Revelando e Reduzindo os Custos Ocultos do AWS Lambda.
Meu percurso pessoal nesse campo 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. Este está quase totalmente construído no 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%. Sem novas funcionalidades, sem picos de tráfego significativos. Inicialmente, atribuí isso a uma 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, percebi que precisava investigar. Não era apenas 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 “Pequenas”
Uma das maiores vantagens do serverless, especialmente para startups ou pequenas equipes, é o generoso nível gratuito. E é generoso! Um milhão de invocações gratuitas por mês para o Lambda, além de uma quantidade significativa de tempo de computação. O problema é que, à medida que sua aplicação cresce, essas invocações “gratuitas” desaparecem mais rápido do que uma fatia de pizza durante um meetup de tecnologia. O que muitas vezes é negligenciado é o volume total das invocações pequenas, aparentemente insignificantes, que se acumulam. Pense nos jobs programados, nas verificações de saúde internas ou até mesmo nos mecanismos de retry provenientes de outros serviços. Cada uma dessas invocações 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. Ela estava programada para ser executada a cada cinco minutos. Parece inofensiva, certo? 288 invocações por dia. Multiplique 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 representa mais de 25.000 invocações apenas para uma pequena tarefa de manutenção. Tínhamos uma dúzia dessas funções. De repente, nossas invocações “pequenas” não eram mais tão pequenas.
Encontrando os Culpados: As Métricas CloudWatch São Seu Melhor Amigo
O primeiro passo para dominar essa besta é saber onde vai seu dinheiro. O AWS CloudWatch é indispensável aqui. Não se limite a olhar o painel de faturamento de alto nível; explore as métricas específicas de suas funções Lambda.
Veja no que me concentrei:
- Invocações: Esta é a métrica mais simples. Altos contadores de invocação para funções que não lidam com tráfego direto do usuário são sinais de alerta imediatos.
- Duração: Quanto tempo cada invocação dura? Durações mais longas significam custos de computação superiores.
- Uso da Memória: Você está superdimensionando a memória para suas funções? Pague pelo que aloca, não pelo que utiliza.
- Taxa de Erros: Altas taxas de erro podem levar a retries, o que significa mais invocações e ciclos de computação desperdiçados.
Para nosso auth-token-refresher, examinei sua métrica `Invocações`. De fato, ela se executava como um relógio, a cada cinco minutos. A duração era mínima, apenas cerca de 50 ms. Mas o volume enorme contribuía para nosso custo total de invocação.
Exemplo Prático 1: Consolidar e Planejar de Forma Mais Inteligente
A solução para auth-token-refresher e várias outras funções de manutenção semelhantes era surpreendentemente simples: a consolidação. Em vez de ter funções Lambda individuais ativadas por eventos CloudWatch (ou EventBridge atualmente) em horários separados, criei uma única função Lambda “Maintenance Runner”.
Este “Maintenance Runner” é ativado por uma única regra de evento CloudWatch, digamos, uma vez por hora. Dentro deste runner, eu tenho um dispatcher simples 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: Atualizar o token de autenticação (executado a cada 5 minutos)
if current_minute % 10 == 0: # Executa agora a cada 10 minutos
print("Executando a atualização do token de autenticação...")
# Chamar a lógica real de atualização do token ou outra função interna
refresh_auth_token()
# Tarefa 2: Limpar logs antigos (executado a cada hora)
if current_hour % 1 == 0 and current_minute == 0: # Executa no início da hora
print("Executando a limpeza dos logs...")
cleanup_old_logs()
# Tarefa 3: Verificar o estado dos serviços externos (executado a cada 30 minutos)
if current_minute == 0 or current_minute == 30:
print("Verificando o estado dos serviços externos...")
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 dos serviços externos ...
pass
Essa simples mudança imediatamente reduziu o número de invocações para essas tarefas de manutenção de centenas de milhares por mês para alguns milhares. As economias foram tangíveis, não apenas em relação às invocações Lambda, mas também em relação à ingestão de logs CloudWatch e chamadas à API Gateway (se alguma delas foi exposta via API Gateway).
O Risco da Sobrallocação de Memória
Este é um outro fator de custo sutil que muitas vezes é negligenciado. Quando você cria uma função Lambda, aloca uma certa quantidade de memória (por exemplo, 128 MB, 256 MB, 512 MB). Você paga por essa memória alocada, independentemente de quanto realmente usa sua função. Além disso, o poder da CPU cresce proporcionalmente à alocação de memória. Portanto, se você alocar 1 GB de memória para um simples script Python que precisa apenas de 128 MB, não só está pagando demais pela memória; você também está pagando por ciclos de CPU adicionais dos quais não precisa.
Aprendi isso da maneira mais difícil com uma função Lambda de processamento de dados que foi inicialmente configurada com 1 GB de memória “só por precaução”. Quando eu examinei suas métricas CloudWatch para o uso de memória, ela sempre permanecia sistematicamente abaixo de 200 MB, mesmo em períodos de alta carga. Em essência, estávamos pagando por 800 MB de RAM não utilizada e pelo correspondente aumento de CPU.
Exemplo Prático 2: Otimizando a Alocação de Memória com Lambda Power Tuning
Determinar manualmente a configuração de memória ideal pode ser tedioso. Você precisa implantar, testar, monitorar, ajustar e repetir. Felizmente, existe uma ótima ferramenta open-source chamada AWS Lambda Power Tuning (desenvolvida por Alex Casalboni na AWS) que simplifica esse processo.
É uma aplicação serverless que ajuda você a visualizar e identificar a configuração de memória ideal para suas funções Lambda, com base nos custos e no desempenho. Você a implanta em sua conta AWS e, em seguida, pode usá-la para testar suas funções.
Aqui está geralmente como funciona:
- 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 seu Lambda várias vezes com configurações de memória diferentes (por exemplo, 128 MB, 256 MB, 512 MB, 1024 MB, etc.).
- Em seguida, 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 função Lambda de processamento de dados, passando pelo Power Tuner, descobri que 256 MB era a melhor escolha em termos de custos, com uma degradação de desempenho insignificante em relação a 1 GB. Imediatamente reduzimos a alocação de memória para 256 MB, o que resultou em uma redução de 75% nos custos de computação para essa função específica. Não foi um caso isolado; desde então, acostumei-me a passar novas funções ou aquelas revisadas por essa ferramenta.
Para utilizá-la, após a implantação, geralmente você iniciaria a máquina de estados com algo como (ajustando o ARN e o payload):
aws stepfunctions start-execution \
--state-machine-arn "arn:aws:states:REGIONE:ID_COMPTE:stateMachine:powerTuningStateMachine" \
--input '{ "lambdaARN": "arn:aws:lambda:REGIONE:ID_COMPTE:function:NOME_DA_SUA_FUNÇÃO", "num": 100, "payload": {}, "parallel": 5 }'
A saída fornece um gráfico claro, mostrando exatamente onde seus custos e sua velocidade se cruzam para um desempenho ideal. É uma mudança significativa para a otimização de custos.
Verbosity dos Logs e Inícios a Frio
Dois outros aspectos que podem muitas vezes surpreendê-lo são a verbosidade 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 é ingerida e armazenada, e você paga por isso. Embora uma boa gravação seja crucial para depuração, uma gravação muito verbosa (por exemplo, imprimir objetos inteiros ou repetir mensagens de estado desnecessariamente) pode rapidamente inflar sua fatura de Logs do CloudWatch.
Eu encontrei algumas funções que registravam o corpo inteiro das requisições HTTP a cada invocação. Embora seja útil para o desenvolvimento inicial, em produção, isso gerava apenas ruído e custava dinheiro. Um ajuste rápido para registrar apenas os metadados essenciais (ID da requisição, código de status, endpoint) reduziu significativamente nossa ingestão de logs.
Os inícios a frio, embora não representem um “custo” direto da mesma forma, influenciam a experiência do usuário e podem indiretamente resultar em mais tentativas ou 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 uma lógica de inicialização complexa fora do manipulador ainda pode fazer a diferença. Para funções críticas sensíveis à latência, a concorrência provisionada é uma opção, mas saiba que você paga por essa concorrência atribuída mesmo quando está inativa.
Exemplo Prático 3: Logs Inteligentes e Variáveis de Ambiente
Para o registro, a solução mais simples é muitas vezes a melhor. Utilize variáveis de ambiente para controlar os níveis de registro. Em Python, por exemplo, você pode fazer assim:
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 o 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 durante o processamento de request_id %s: %s", event.get('request_id'), str(e), exc_info=True)
return {
'statusCode': 500,
'body': 'Erro'
}
Definindo LOG_LEVEL como INFO em produção e DEBUG em desenvolvimento/staging, você pode reduzir significativamente sua fatura de Logs do CloudWatch sem sacrificar a observabilidade quando precisar.
Outro conselho é prestar atenção ao 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 custosas como o pooling de conexões com o banco de dados ou grandes importações de bibliotecas, considere adiar sua execução até que sejam realmente necessárias no manipulador, ou certifique-se de que estejam efetivamente armazenadas em cache para as próximas invocações a quente.
Pontos Chave para Sua Cruzada de Custos Sem Servidor
Ok, cobrimos muita coisa. Aqui está um resumo das etapas práticas que você pode tomar imediatamente para começar a reduzir esses custos traiçoeiros de Lambda:
“`html
- Monitore sem parar: Não se limite a dar uma olhada na sua fatura global 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 inesperados.
- Consolide as tarefas agendadas: Se você tiver várias pequenas funções Lambda programadas, considere combiná-las em um único “Maintenance Runner” que distribui as tarefas de acordo com uma programação menos frequente. Isso reduz drasticamente o número de invocações.
- Otimize a alocação de memória: Use ferramentas como AWS Lambda Power Tuning para encontrar a configuração de memória ideal para suas funções. Não faça suposições e não sobreprovisione. Lembre-se, mais memória significa mais CPU, e você paga por ambas.
- Verifique a verbosidade dos logs: Implemente níveis de logging guiados por variáveis de ambiente (por exemplo,
INFOpara produção,DEBUGpara desenvolvimento). Evite registrar o corpo das solicitações inteiras ou um estado interno excessivo em produção. Sua fatura do CloudWatch Logs agradece. - Revise funções não utilizadas: Audite periodicamente suas funções Lambda. Existem funções antigas, experimentais ou obsoletas ainda ativas e gerando custos? Elimine-as!
- Monitore o tamanho dos pacotes: Pacotes de deploy menores significam inícios a frio mais rápidos e custos de armazenamento mais baixos. Inclua apenas as dependências necessárias.
- Compreenda seu modelo de preços: Revise 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.
Dominar o monstro sem servidor não é uma questão de evitar o sem servidor; é sobre ser astuto e intencional na forma como o utilizamos. A flexibilidade e a escalabilidade são inestimáveis, mas sem a vigilância adequada, esses “pequenos” custos podem se acumular a ponto de representar uma parte significativa do seu orçamento. Vá em frente, monitore, otimize e economize!
Isso é tudo por hoje. Deixe-me saber nos comentários se você tem outras sugestões ou dicas para a otimização de custos do Lambda!
“`
🕒 Published:
Related Articles
- LangSmith vs Weights & Biases : Qual escolher para equipes pequenas
- Spedisci più velocemente, non più duramente: Suggerimenti sulle prestazioni che si scalano realmente
- Ich habe die Kaltstarts ohne Server für die Leistung des Agents optimiert.
- Meus custos de nuvem prejudicam minhas margens de lucro (e as suas)