Olá a todos, Jules Martin aqui, de volta ao agntmax.com!
Hoje, quero falar sobre algo que me preocupa, e provavelmente muitos de vocês também, há cerca de um ano: o aumento gradual dos custos de infraestrutura em nuvem, especialmente quando se trata de funções sem servidor. Todos nós fomos seduzidos pelo sonho de “pague pelo que você usa”, e por muito tempo, isso parecia ser uma realidade. Mas ultimamente, vi as faturas subirem, às vezes de maneira incompreensível, mesmo quando os padrões de tráfego pareciam estáveis. É como se estivéssemos sendo devorados pela própria flexibilidade que adotamos. Então, vamos explorar algo muito específico e atual: Dominar o Monstro Sem Servidor: Revelar e Reduzir os Custos Ocultos do AWS Lambda.
Minha própria jornada neste campo começou há cerca de seis meses. Temos um microserviço central que gerencia a autenticação de usuários e o gerenciamento de sessões. Ele é quase totalmente construído sobre AWS Lambda, API Gateway, DynamoDB e Cognito. Por muito tempo, os custos eram perfeitamente previsíveis. Então, no último verão, nossa fatura AWS para este serviço específico subiu cerca de 15%. Sem novas funcionalidades, sem picos significativos de tráfego. A princípio, atribuí isso a uma flutuação sazonal ou a um pequeno bug que ainda não havia descoberto. Mas quando a fatura do mês seguinte chegou ainda mais alta, percebi que precisava investigar. Não era apenas um incidente isolado; era uma tendência, e isso estava realmente nos custando dinheiro.
A Ilusão dos Níveis “Gratuitos” e a Realidade das Invocações “Mínimas”
Um dos principais argumentos de venda das soluções sem servidor, especialmente para startups ou pequenas equipes, é o generoso nível gratuito. E ele é 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, 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 considerável de invocações mínimas, aparentemente sem importância, que se acumulam. Pense nas tarefas cron, nas verificações de saúde internas ou até mesmo nos mecanismos de retry 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. Isso parece inofensivo, não é? 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, isso soma 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 assim.
Encontrando os Culpados: As Métricas do CloudWatch São Seus Melhores Amigos
A primeira etapa para domar essa besta é saber para onde vai seu dinheiro. O AWS CloudWatch é indispensável aqui. Não se contente em olhar o painel de faturamento em um nível elevado; explore as métricas específicas para suas funções Lambda.
Aqui está no que me concentrei:
- Invocações: Esta é a métrica mais simples. Contagens elevadas de invocações para funções que não gerenciam tráfego de usuário direto são sinais de alerta imediatos.
- Duração: Quanto tempo cada invocação leva para ser executada? Durações mais longas significam custos de computação mais altos.
- Uso de Memória: Você sobreprovisionou memória para suas funções? Você paga pelo que aloca, não pelo que utiliza.
- Taxa de Erro: Taxas de erro elevadas podem levar a retrials, 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 rodava como um relógio, a cada cinco minutos. A duração era mínima, apenas cerca de 50ms. Mas o volume considerável contribuía para nosso custo global 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 acionadas por eventos do CloudWatch (ou EventBridge nos dias de hoje) em horários separados, criei uma única função Lambda chamada “Maintenance Runner”.
Esse “Maintenance Runner” é acionado por uma única regra de evento do CloudWatch, digamos, uma vez por hora. Dentro desse runner, tenho um simples dispatcher 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 (executava a cada 5 mins)
if current_minute % 10 == 0: # Executar 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 os logs antigos (executava a cada hora)
if current_hour % 1 == 0 and current_minute == 0: # Executar no início da hora
print("Executando a limpeza dos logs...")
cleanup_old_logs()
# Tarefa 3: Verificar o status do serviço externo (executava a cada 30 mins)
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 de 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 o número de invocações para essas tarefas de manutenção de centenas de milhares por mês para alguns milhares. As economias feitas foram tangíveis, não apenas em termos de invocações Lambda, mas também em relação à ingestão dos logs do CloudWatch e às chamadas ao API Gateway (se alguma dessas chamadas estivesse exposta via API Gateway).
O Perigo da Sobreprovisionamento de Memória
Isso representa outro fator de custo sutil que é frequentemente negligenciado. Quando você cria uma função Lambda, você aloca uma certa quantidade de memória (por exemplo, 128MB, 256MB, 512MB). Você paga por essa memória alocada, não importa quanto sua função realmente use. Além disso, o poder de CPU aumenta proporcionalmente à alocação de memória. Assim, se você alocar 1 GB de memória para um simples script Python que só precisa de 128 MB, você não está apenas pagando demais pela memória; você também está pagando demais pelos ciclos de CPU que não são necessários.
Aprendi isso da maneira difícil com uma função Lambda de processamento de dados que estava inicialmente configurada com 1 GB de memória “só para garantir”. Quando olhei suas métricas do CloudWatch para o uso de memória, ela permanecia constantemente abaixo de 200 MB, mesmo durante os picos de carga. Estávamos essencialmente pagando por 800 MB de RAM não utilizada e o aumento de CPU correspondente.
Exemplo Prático 2: Otimizar a Alocação de Memória com o Lambda Power Tuning
Determinar manualmente a configuração de memória ideal pode ser trabalhoso. Você precisa implantar, testar, monitorar, ajustar e repetir. Felizmente, há uma ótima ferramenta de código aberto chamada AWS Lambda Power Tuning (desenvolvida por Alex Casalboni na AWS) que facilita esse processo.
É uma aplicação sem servidor que ajuda você a visualizar e identificar a configuração de memória ideal para suas funções Lambda, com base em custos e desempenho. Você a implanta em sua conta AWS, e então pode usá-la para testar suas funções.
Veja como isso geralmente funciona:
- Você implanta a ferramenta Power Tuning através do Serverless Application Repository ou SAM.
- Você invoca uma máquina de estado (criada pela ferramenta) com a ARN da sua função Lambda e uma carga útil.
- A máquina de estado invoca sua Lambda várias vezes com diferentes configurações de memória (por exemplo, 128 MB, 256 MB, 512 MB, 1024 MB, etc.).
- Então, ela analisa os logs de execução e fornece uma visualização mostrando os compromissos entre custos e velocidade para cada configuração de memória.
Para minha função Lambda de processamento de dados, o teste com o Power Tuner mostrou que 256 MB era o meio-termo ideal para o custo, com uma degradação de desempenho negligenciável 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 evento isolado; desde então, estabeleci como prática padrão fazer a migração de novas funções ou aquelas reavaliadas por essa ferramenta.
Para utilizá-lo, após a implantação, você começaria tipicamente a máquina de estados com algo como isto (ajustando o ARN e a carga útil):
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 }'
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.
A Verbosidade dos Logs e os Inícios a Frio
Outras duas áreas que costumam surpreender são a verbosidade dos logs e os inícios a frio. Os logs do CloudWatch não são gratuitos. Cada linha impressa pela sua função Lambda é ingerida e armazenada, e você paga por isso. Embora um bom log seja crucial para a depuração, uma logagem excessivamente verbosa (por exemplo, imprimir objetos inteiros ou repetir desnecessariamente mensagens de status) pode rapidamente aumentar sua conta de logs do CloudWatch.
Encontrei algumas funções que registravam o corpo completo da requisição HTTP a cada invocação. Embora isso seja útil para o desenvolvimento inicial, em produção, era apenas ruído e custo. Um ajuste rápido para registrar apenas os metadados essenciais (ID da requisição, código de estado, ponto de extremidade) reduziu consideravelmente nossa ingestão de logs.
Os inícios a frio, embora não sejam um “custo” direto da mesma forma, impactam a experiência do usuário e podem indiretamente resultar em mais tentativas ou em períodos de cobrança mais longos se sua função precisar esperar por recursos. Embora a AWS tenha feito progresso significativo em reduzir os 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 esteja ciente de que você paga por essa concorrência alocada mesmo quando ela está ociosa.
Exemplo Prático 3: Logagem Inteligente e Variáveis de Ambiente
Para a logagem, a solução mais simples é geralmente a melhor. Use variáveis de ambiente para controlar os níveis de logagem. Em Python, por exemplo, você pode fazer isto:
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("Finalizado o processamento para request_id: %s", event.get('request_id'))
return {
'statusCode': 200,
'body': 'Sucesso'
}
except Exception as e:
logger.error("Erro ao processar 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 consideravelmente sua conta do CloudWatch Logs sem sacrificar a observabilidade quando necessário.
Outro conselho é prestar atenção ao que é inicializado fora do manipulador. Todo código diretamente no escopo global da sua função Lambda será executado durante o início a frio. Se você tem operações custosas como pooling de conexões com o banco de dados ou imports de bibliotecas grandes, considere adiá-las até que sejam realmente necessárias no manipulador, ou assegure-se de que sejam efetivamente armazenadas em cache para as invocações quentes seguintes.
Ações Concretas para Sua Cruzada de Custos Serverless
Bem, cobrimos bastante coisa. Aqui está um resumo das etapas práticas que você pode tomar agora mesmo para começar a reduzir esses custos Lambda sorrateiros:
- Monitore constantemente: Não se limite a dar uma olhada na sua conta AWS geral. Explore as métricas do CloudWatch para as Invocações, Duração e Uso da Memória para cada função Lambda. Configure alarmes para picos inesperados.
- Consolide as tarefas cron: Se você tem muitas pequenas funções Lambda agendadas, considere agrupá-las em um único “Mantenedor” que distribui as tarefas de acordo com um cronograma menos frequente. Isso reduz consideravelmente 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 adivinhe nem superprovisionar. Lembre-se de que mais memória significa mais CPU, e você paga por ambos.
- Controle a verbosidade dos logs: Implemente níveis de logagem controlados por variáveis de ambiente (por exemplo,
INFOpara produção,DEBUGpara desenvolvimento). Evite registrar integralmente os corpos de requisição ou estados internos excessivos em produção. Sua conta do CloudWatch Logs ficará grata. - Examine funções não utilizadas: Audite periodicamente suas funções Lambda. Há funções antigas, experimentais ou obsoletas ainda ativas e gerando custos? Apague-as!
- Monitore o tamanho dos pacotes: Pacotes de implantação menores significam inícios a frio mais rápidos e menos custos de armazenamento. Inclua apenas as dependências necessárias.
- Entenda seu modelo de precificação: Releia a página de preços do Lambda. Compreenda como as invocações, os GB-segundos e o tráfego de dados são cobrados. O conhecimento é poder, especialmente quando se trata do seu orçamento.
Dominar o monstro serverless não consiste em evitar o serverless; trata-se de ser inteligente e intencional em nosso uso. A flexibilidade e a escalabilidade são inestimáveis, mas sem vigilância adequada, esses “pequenos” custos podem se acumular e representar uma parte significativa do seu orçamento. Vá em frente, monitore, otimize e economize!
É tudo por hoje. Deixe-me saber nos comentários se você tem outras dicas ou truques para otimização de custos Lambda!
🕒 Published: