O blog da AWS

Melhores práticas de observabilidade para funções duráveis do Lambda

Por D Surya Sai e Udit Parikh, da Amazon Web Services.

Quando seu fluxo de trabalho é suspenso para aguardar uma confirmação, você precisa saber se o callback chegou, quanto tempo a função aguardou e o que fazer se o callback nunca chegar. As funções duráveis do AWS Lambda tornam esses fluxos de trabalho de longa duração e suspensíveis simples de construir, mas responder a essas questões operacionais requer instrumentação de monitoramento deliberada através do limite de suspensão.

Nesta publicação, percorremos as melhores práticas de observabilidade para funções duráveis do Lambda usando um pipeline de processamento de pagamentos Stripe como exemplo. Cobrimos métricas específicas de funções duráveis do Amazon CloudWatch, métricas de negócios personalizadas, alarmes, registro estruturado, rastreamento do AWS X-Ray e como depurar um timeout de callback de ponta a ponta. Ao final, você terá um padrão de observabilidade reutilizável para qualquer função durável que suspenda em callbacks externos. O repositório GitHub contém a implementação completa.

Visão geral da arquitetura

Nossa aplicação processa pagamentos com cartão através do Stripe usando três funções Lambda e o Amazon API Gateway:

1. Payment API (payment-api): Uma função apoiada pelo API Gateway que aceita solicitações de pagamento, invoca assincronamente a função durável e expõe endpoints para verificar ou cancelar uma execução em andamento.

2. Payment Processor (payment-processor): Uma função durável que valida o pagamento, cria um PaymentIntent do Stripe, então suspende e aguarda um callback confirmando o resultado do pagamento.

3. Webhook Handler (stripe-webhook): Recebe eventos de webhook do Stripe, verifica a assinatura e chama send_durable_execution_callback_success para retomar a execução durável suspensa com o resultado do pagamento.

Diagrama de arquitetura mostrando o fluxo de processamento de pagamento com suspensão de callback durável

Figura 1: Fluxo de processamento de pagamento com suspensão de callback durável, onde o manipulador de webhook envia o resultado do callback de volta para a mesma execução durável suspensa

O principal desafio de observabilidade está na lacuna entre a criação do PaymentIntent (etapa 2) e a entrega do webhook (etapa 3). Durante este período, a função durável está suspensa: não está consumindo computação, mas está aguardando o Stripe retornar a chamada. Se o webhook nunca chegar, o callback expira silenciosamente, a menos que você tenha métricas e alarmes observando isso. Com instrumentação adequada, você obtém visibilidade completa dessa lacuna de suspensão e pode diagnosticar problemas em minutos.

Você implanta a aplicação com o AWS Serverless Application Model (AWS SAM). O trecho de template a seguir mostra como habilitamos a observabilidade em toda a pilha:

Globals:
  Function:
    Runtime: python3.13
    Tracing: Active # X-Ray on all functions
    Environment:
      Variables:
        POWERTOOLS_METRICS_NAMESPACE: DurablePayments
        LOG_LEVEL: INFO

Resources:
  PaymentApi:
    Type: AWS::Serverless::Api
    Properties:
      TracingEnabled: true # X-Ray on API Gateway

  PaymentProcessorFunction:
    Type: AWS::Serverless::Function
    Properties:
      AutoPublishAlias: live
      DurableConfig:
        ExecutionTimeout: 600 # Bounds the whole workflow
        RetentionPeriodInDays: 5 # Keep execution history

Tracing: Active em Globals habilita o X-Ray em todas as funções, e TracingEnabled: true no recurso da API garante que os rastreamentos se propaguem desde a solicitação inicial através de todo o fluxo.

Métricas do CloudWatch para funções duráveis, métricas de negócios personalizadas e alarmes

O Lambda emite automaticamente métricas do CloudWatch específicas para execuções duráveis, cobrindo o ciclo de vida da execução, utilização de capacidade, duração incluindo tempo de espera e direcionadores de custo. Para a lista completa, consulte Monitoramento de funções duráveis.

Uma métrica que vale a pena destacar: DurableExecutionDuration mede o tempo total de relógio de parede incluindo o período de espera do callback. Para um pagamento que leva 2 segundos para processar mas aguarda 30 segundos por um webhook, esta métrica reporta aproximadamente 32 segundos. Isso é distinto da métrica padrão Duration, que mede apenas o tempo de computação ativo.

Métricas de negócios personalizadas para o funil de callback

As métricas integradas informam se as execuções tiveram sucesso ou falharam. Para entender onde no fluxo de negócios o problema ocorreu, emitimos métricas personalizadas em cada estágio usando Powertools for AWS Lambda Metrics com Embedded Metric Format (EMF):

from aws_lambda_powertools import Metrics
from aws_lambda_powertools.metrics import MetricUnit

metrics = Metrics(namespace="DurablePayments", service="payment-processor")

# In the durable handler, after each stage:
metrics.add_metric(name="PaymentIntentCreated", unit=MetricUnit.Count, value=1)
metrics.add_metric(name="PaymentSucceeded", unit=MetricUnit.Count, value=1)
metrics.add_metric(name="PaymentFailed", unit=MetricUnit.Count, value=1)
metrics.add_metric(name="PaymentTimeout", unit=MetricUnit.Count, value=1)

No manipulador de webhook:

metrics.add_metric(name="WebhookReceived", unit=MetricUnit.Count, value=1)
metrics.add_metric(name="WebhookSucceeded", unit=MetricUnit.Count, value=1)
metrics.add_metric(name="WebhookSignatureFailure", unit=MetricUnit.Count, value=1)

Essas métricas criam um funil de ponta a ponta:

PaymentRequested → PaymentIntentCreated → WebhookReceived → WebhookSucceeded → PaymentSucceeded

Qualquer queda entre os estágios identifica o problema. Se PaymentIntentCreated for maior que WebhookReceived, o Stripe não está entregando webhooks. Se WebhookReceived for maior que WebhookSucceeded, a verificação de assinatura está falhando. Nenhum PaymentSucceeded correspondente para um PaymentIntentCreated significa que o callback expirou.

Alarmes para modos de falha de callback

Funções duráveis com callbacks têm modos de falha específicos: callbacks que nunca chegam, assinaturas de webhook que falham na verificação e execuções que expiram aguardando. Definimos alarmes para cada um:

DurableExecutionFailureAlarm:
  Type: AWS::CloudWatch::Alarm
  Properties:
    Namespace: AWS/Lambda
    MetricName: DurableExecutionFailed
    Dimensions:
      - Name: FunctionName
        Value: !Ref PaymentProcessorFunction
    Threshold: 1
    ComparisonOperator: GreaterThanOrEqualToThreshold
    TreatMissingData: notBreaching
    ...

PaymentTimeoutAlarm:
  Type: AWS::CloudWatch::Alarm
  Properties:
    Namespace: DurablePayments
    MetricName: PaymentTimeout
    Dimensions:
      - Name: service
        Value: payment-processor
    Threshold: 1
    ...

WebhookSignatureFailureAlarm:
  Type: AWS::CloudWatch::Alarm
  Properties:
    Namespace: DurablePayments
    MetricName: WebhookSignatureFailure
    Dimensions:
      - Name: service
        Value: stripe-webhook
    Threshold: 3

Essas definições de alarme são abreviadas para legibilidade. Cada alarme no template.yaml implantado também define Dimensions (delimitando DurableExecutionFailed para a função payment-processor e as métricas personalizadas para seus serviços). Também inclui Statistic, Period, EvaluationPeriods e AlarmActions/OKActions conectados a um tópico SNS. Consulte o repositório GitHub para as definições implantáveis.

Alarme O que ele captura
DurableExecutionFailed Erros de código, falhas da API do Stripe, exceções não tratadas na função durável
DurableExecutionTimedOut Timeout de execução completa: execução excede DurableConfig.ExecutionTimeout
PaymentTimeout Callbacks que nunca chegam: configuração incorreta de webhook, interrupção do Stripe, problemas de rede
WebhookSignatureFailure Segredo de webhook errado, ataques de replay, configuração incorreta de endpoint
WebhookError Picos de erro na função de webhook (exceções não tratadas no manipulador)

Painel unificado

Combinamos métricas duráveis integradas, métricas EMF personalizadas e métricas padrão do Lambda em um único painel do CloudWatch. O painel inclui widgets para estado de execução, resultados de pagamento, métricas de fluxo de ponta a ponta, utilização de cota, direcionadores de custo, detalhamento de erros e latência de API/webhook.

Painel do CloudWatch mostrando estado de execução durável, resultados de pagamento e métricas de fluxo de ponta a ponta

Figura 2: Painel do CloudWatch mostrando estado de execução durável, resultados de pagamento, métricas de fluxo de ponta a ponta, execuções em andamento e utilização de cota

Painel de Alarmes do CloudWatch mostrando estados de alarme de DurableExecutionFailures, PaymentTimeouts e WebhookSignatureFailures

Figura 3: Alarmes do CloudWatch mostrando estados de alarme de DurableExecutionFailures, PaymentTimeouts e WebhookSignatureFailures

Rastreando callbacks através do limite de suspensão

Quando uma função durável suspende em um callback, a execução pausa. Um sistema externo (Stripe) dispara um webhook para seu API Gateway, que invoca o manipulador de webhook. O manipulador de webhook então chama send_durable_execution_callback_success para entregar o resultado de volta à execução suspensa, que retoma e completa. O desafio é correlacionar essas duas invocações separadas para que você possa reconstruir a linha do tempo completa do pagamento a partir de uma única consulta.

Registro estruturado com chaves de correlação

Usando o Lambda Powertools Logger, acrescentamos progressivamente chaves de correlação à medida que ficam disponíveis. Cada entrada de log subsequente inclui automaticamente todas as chaves acrescentadas anteriormente:

from aws_lambda_powertools import Logger
from aws_durable_execution_sdk_python import (
    DurableContext, durable_execution, durable_step,
)
from aws_durable_execution_sdk_python.config import CallbackConfig, Duration
from aws_durable_execution_sdk_python.exceptions import CallbackError

logger = Logger(service="payment-processor")

@durable_execution
def handler(event, context: DurableContext):
    payment = context.step(validate_payment_request(event), name="validate-payment")
    logger.append_keys(customer_id=payment["customer_id"])

    callback = context.create_callback(
        name="stripe-payment-result",
        config=CallbackConfig(timeout=Duration.from_minutes(5)),
    )
    logger.info("Callback created", callback_id=callback.callback_id)

    intent = context.step(
        create_stripe_payment_intent(payment, callback.callback_id),
        name="create-payment-intent",
    )
    logger.append_keys(payment_intent_id=intent["payment_intent_id"])
    logger.info("Suspending, waiting for Stripe webhook callback")

    try:
        result = callback.result()  # Function suspends here
    except CallbackError:
        logger.warning("Payment timed out")
        return {"status": "timeout", "message": "No confirmation within 5 minutes"}

No manipulador de webhook, acrescentamos as mesmas chaves para que uma única consulta do Logs Insights reconstrua a linha do tempo completa:

logger = Logger(service="stripe-webhook")

def handler(event, context):
    # ... verify signature, parse event
    logger.append_keys(event_type=event_type, payment_intent_id=payment_intent_id)
    logger.append_keys(callback_id=callback_id)
    logger.info("Processing webhook event")

Consulte todos os três grupos de logs para um único pagamento:

fields @timestamp, service, message, customer_id, payment_intent_id, callback_id
| filter payment_intent_id = "pi_3TJafD04vzZc6RmP0RrCWhix"
| sort @timestamp asc
Consulta do CloudWatch Logs Insights mostrando a linha do tempo de um único pagamento através de payment-api, payment-processor e stripe-webhook

Figura 4: Consulta do CloudWatch Logs Insights mostrando a linha do tempo de um único pagamento através de payment-api, payment-processor e stripe-webhook

Etapas duráveis e anotações do X-Ray

O decorador @durable_step do SDK cria pontos de verificação em cada etapa. Se a função falhar e repetir, as etapas concluídas retornam seu resultado em cache sem re-executar. Combinamos isso com o Powertools Tracer para adicionar anotações pesquisáveis do X-Ray em cada ponto crítico de negócios:

from aws_durable_execution_sdk_python import StepContext, durable_step

@durable_step
@tracer.capture_method
def create_stripe_payment_intent(step_context: StepContext, payment: dict, callback_id: str) -> dict:
    tracer.put_annotation("callback_id", callback_id)
    tracer.put_annotation("customer_id", payment["customer_id"])

    try:
        intent = stripe.PaymentIntent.create(
            amount=payment["amount"], currency=payment["currency"],
            payment_method=payment["payment_method_id"], confirm=True,
            metadata={"callback_id": callback_id},
            automatic_payment_methods={"enabled": True, "allow_redirects": "never"},
            ...
        )
    except stripe.error.CardError as exc:
        # Hard declines (e.g. pm_card_chargeDeclined) raise synchronously. Return a
        # structured decline so the step doesn't retry and fail the whole execution.
        ...
        metrics.add_metric(name="PaymentDeclinedAtCreate", unit=MetricUnit.Count, value=1)
        return {"declined": True, ...}  # decline_code, error_message, payment_intent_id

    metrics.add_metric(name="PaymentIntentCreated", unit=MetricUnit.Count, value=1)
    ...
    return {"payment_intent_id": intent.id, "status": intent.status}

Nota: O código anterior é abreviado para legibilidade. Consulte o repositório GitHub para o código completo. O manipulador durável principal é executado dentro de um contexto X-Ray FacadeSegment que não suporta put_annotation(). As anotações funcionam normalmente dentro de funções @durable_step. No manipulador principal, use um wrapper try/except se precisar de anotações fora das etapas.

Nota: Ao chamar PaymentIntent.create com confirm=True, alguns cartões são recusados sincronamente (nenhum webhook é disparado). O código implantado lida com isso detectando a recusa no valor de retorno da etapa e pulando a suspensão do callback, evitando uma espera indefinida.

O Mapa de Serviço do X-Ray mostra o fluxo completo da solicitação: API Gateway para payment-api para payment-processor, e o caminho separado do webhook do API Gateway para stripe-webhook.

Mapa de Serviço do X-Ray mostrando API Gateway conectado a payment-api e stripe-webhook, com payment-api conectado a payment-processor

Figura 5: Mapa de Serviço do X-Ray mostrando API Gateway conectado a payment-api e stripe-webhook, com payment-api conectado a payment-processor

Aba de execuções duráveis

O console do Lambda fornece uma aba integrada de Execuções duráveis mostrando a linha do tempo passo a passo de cada execução, incluindo o estado de espera do callback. Você pode ver quais etapas foram concluídas, onde a função suspendeu e quando (ou se) o callback chegou.

Aba de Execuções duráveis do console do Lambda mostrando uma execução concluída com etapas: validate-payment bem-sucedida, create-payment-intent bem-sucedida, callback stripe-payment-result recebido e resultado final bem-sucedido

Figura 6: Aba de Execuções duráveis do console do Lambda mostrando uma execução concluída com etapas: validate-payment bem-sucedida, create-payment-intent bem-sucedida, callback stripe-payment-result recebido e resultado final bem-sucedido

Juntando tudo: depurando modos de falha reais

Os três cenários a seguir demonstram como todas essas camadas de observabilidade funcionam juntas. Você pode reproduzir cada um deles na página de checkout de demonstração.

Cenário 1: Webhook nunca chega

Um cliente relata que seu pagamento foi cobrado, mas nunca recebeu uma confirmação.

1. Alarme dispara. O PaymentTimeoutAlarm é acionado, indicando que uma execução durável expirou aguardando um callback.

2. Verifique o painel. O widget de Resultados de Pagamento mostra um pico em PaymentTimeout. O widget de Métricas de Fluxo de Ponta a Ponta revela a queda: a contagem de PaymentIntentCreated é maior que WebhookReceived, significando que o webhook nunca chegou.

3. Consulte os logs. Pesquise no Amazon CloudWatch Logs Insights pelo pagamento que expirou:

fields @timestamp, service, message, payment_intent_id, callback_id
| filter message = "Payment timed out"
| sort @timestamp desc
| limit 5

Isso retorna o payment_intent_id do pagamento que expirou.

4. Faça referência cruzada com o manipulador de webhook. Pesquise por esse payment_intent_id nos logs do manipulador de webhook. Nenhum resultado significa que o Stripe nunca entregou o webhook. Resultados com WebhookSignatureFailure significam que o segredo do webhook está configurado incorretamente.

5. Inspecione o rastreamento do X-Ray. Filtre rastreamentos pela anotação payment_intent_id. O rastreamento mostra o início da função durável, mas nenhum span correspondente do manipulador de webhook, confirmando que o webhook nunca chegou.

6. Verifique a aba de execuções duráveis. A execução mostra validate-payment e create-payment-intent como bem-sucedidas, com o callback stripe-payment-result em um estado de timeout.

Aba de execuções duráveis mostrando a execução que expirou: validate-payment bem-sucedida, create-payment-intent bem-sucedida, callback stripe-payment-result expirou

Figura 7: Aba de execuções duráveis mostrando a execução que expirou: validate-payment bem-sucedida, create-payment-intent bem-sucedida, callback stripe-payment-result expirou

Em minutos, você identificou a causa raiz (o endpoint de webhook do Stripe estava configurado incorretamente) sem adicionar uma única instrução de depuração ou reimplantar código.

Cenário 2: O fluxo de trabalho inteiro é executado por muito tempo

O timeout de callback no Cenário 1 é um limite por callback (5 minutos neste exemplo). Há também um limite externo: DurableConfig.ExecutionTimeout (600 segundos), que limita o tempo total de relógio de parede de toda a execução. Se você definir um callback para aguardar uma hora, mas o ExecutionTimeout geral for de 10 minutos, a própria execução termina primeiro. Isso aparece como um estado terminal distinto na aba de execuções duráveis, no widget de Estado de Execução Durável e como seu próprio alarme (DurableExecutionTimedOutAlarm).

Escolha a opção “Simulate timeout (no webhook)” na página de checkout de demonstração para reproduzir isso. A função durável pula a chamada do Stripe, suspende em um callback de timeout longo e deixa o ExecutionTimeout capturá-lo. O painel distingue os dois modos de falha claramente: timeouts por callback aparecem no widget personalizado de Resultados de Pagamento como PaymentTimeout. Timeouts de execução completa aparecem no widget integrado de Estado de Execução Durável junto com contagens de iniciadas/bem-sucedidas/falhas. Essa distinção importa operacionalmente porque a remediação é diferente: timeouts de callback apontam para problemas de sistema externo (Stripe), enquanto timeouts de execução apontam para problemas de configuração (seus valores de timeout).

Cenário 3: Cliente abandona o checkout

Fluxos de checkout reais têm um terceiro resultado: o cliente cancela enquanto a função durável ainda está suspensa. A demonstração conecta isso a StopDurableExecution, que termina a execução em andamento e aparece no mesmo widget de Estado de Execução Durável como um estado terminal separado.

Escolha “Simulate timeout” e depois “Cancel Payment” na página de demonstração para ver isso acontecer. Olhando para o painel após executar todos os três cenários, o widget de estado de execução conta a história completa: iniciada, bem-sucedida, falha, expirada e parada. Cada estado responde a uma questão operacional diferente sobre o que está acontecendo com seus fluxos de trabalho.

Conclusão

Nesta publicação, percorremos as melhores práticas de observabilidade para funções duráveis do Lambda usando um pipeline de processamento de pagamentos Stripe. Callbacks podem expirar, execuções inteiras podem expirar e fluxos de trabalho em execução podem ser cancelados. Cada um aparece como um estado terminal distinto, e cada um merece seu próprio alarme. Camadas de métricas de negócios personalizadas, registro estruturado com chaves de correlação, anotações do X-Ray e a aba de execuções duráveis sobre as métricas integradas do CloudWatch fornecem uma imagem clara de onde no ciclo de vida qualquer execução está. Também revela onde no funil de negócios qualquer falha ocorreu.

Implante a aplicação de processamento de pagamentos do repositório GitHub e experimente os três cenários de demonstração para ver os painéis, alarmes e histórico de execução em sua própria conta. Para conceitos fundamentais, consulte Funções duráveis do Lambda. Para o SDK de execução durável, consulte o SDK Python, SDK JavaScript e SDK Java. Navegue pelo Serverless Land para arquiteturas de referência.


Este conteúdo foi traduzido do post original do blog, que pode ser encontrado aqui.

Tradutores

Nicolas Tarzia é Senior Technical Account Manager na AWS, com mais de 13 anos de experiência, com ampla experiência em arquitetura cloud, engenharia e design de software. Sua área de interesse são tecnologias serverless.
https://www.linkedin.com/in/nicolastarzia
Daniel Abib é Arquiteto de Soluções Sênior e Especialista em Amazon Bedrock na AWS, com mais de 25 anos trabalhando com gerenciamento de projetos, arquiteturas de soluções escaláveis, desenvolvimento de sistemas e CI/CD, microsserviços, arquitetura Serverless & Containers e especialização em Machine Learning. Ele trabalha apoiando Startups, ajudando-os em sua jornada para a nuvem.
https://www.linkedin.com/in/danielabib/