AWS Builder Center
Prevenção de Falhas em Cascata: Implementando o Padrão Circuit Breaker Distribuído com Amazon DynamoDB e AWS Lambda

Prevenção de Falhas em Cascata: Implementando o Padrão Circuit Breaker Distribuído com Amazon DynamoDB e AWS Lambda

O artigo detalha a implementação de um Circuit Breaker Distribuído de alta performance utilizando o Amazon DynamoDB como repositório centralizado de estado e funções AWS Lambda como interceptadores hexagonais. Orientado a arquitetos de soluções seniores, o material explora o uso de contadores atômicos, gravações condicionais para mitigação de condições de corrida e sondagens automatizadas (Half-Open state), assegurando resiliência extrema em integrações síncronas de longa duração.

Systems Architect na NTT DATA, Estudante Doutorado na MUST University, 12x AWS e ASD & ADHD
A integração com serviços de terceiros, como gateways de pagamento ou sistemas legados corporativos, introduz pontos únicos de falha sistêmica em ecossistemas de microsserviços. Quando uma dependência externa sofre degradação de performance, as chamadas síncronas acumulam latência exponencialmente. Mecanismos ingênuos de repetição implementados pelos clientes amplificam a carga destrutiva, resultando em tempestades de requisições (retry storms) que esgotam a concorrência computacional da AWS Lambda e causam falhas em cascata por toda a plataforma. A implementação do padrão Circuit Breaker Distribuído resolve essa vulnerabilidade estrutural. Ao centralizar o estado de integridade das dependências no Amazon DynamoDB e avaliar o circuito em milissegundos antes de qualquer invocação de rede, a arquitetura implementa o conceito de falha rápida (fail-fast). Essa topologia protege os recursos computacionais internos, descarta tráfego de rede excedente e executa sondagens automatizadas para autorrecuperação, garantindo resiliência inquebrável para cargas de trabalho de missão crítica hospedadas na AWS.

Pré-requisitos

O provisionamento desta barreira de resiliência demanda proficiência em modelagem NoSQL e controle de concorrência distribuída. O ambiente necessita de orquestração via Terraform versão 1.7.0 ou superior e o provedor HashiCorp AWS versão 5.40.0. A camada de integração requer o Python 3.12, incorporando a biblioteca boto3 versão 1.34.0 e o aws-lambda-powertools versão 2.34.0. Privilégios administrativos no AWS IAM são vitais para configurar políticas estritas que permitam as operações dynamodb:UpdateItem e dynamodb:GetItem exclusivamente na tabela de controle de estado.

Passo a Passo

O Repositório de Estado de Alta Performance

A fundação do Circuit Breaker em arquiteturas sem servidor requer a externalização do estado para um repositório centralizado e de altíssima velocidade. Diferente de aplicações em contêineres de longa duração que mantêm o estado do circuito na memória RAM local, funções efêmeras desconhecem o histórico de falhas das invocações vizinhas. Provisionamos o Amazon DynamoDB para atuar como o cérebro do circuito. A justificativa técnica para essa escolha é a latência de leitura em milissegundos de um único dígito e o suporte a consistência forte (Strongly Consistent Reads). Modelamos a tabela utilizando o nome da integração externa (por exemplo, PaymentGateway) como chave de partição. A tabela armazenará o estado atual (CLOSED, OPEN, HALF_OPEN), um contador de falhas consecutivas e um carimbo de tempo para expiração do estado aberto. O faturamento por capacidade sob demanda (Pay-Per-Request) garante que a infraestrutura escale instantaneamente durante flutuações violentas de tráfego sem custos ociosos.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
resource "aws_dynamodb_table" "circuit_breaker_state" {
name = "DistributedCircuitBreaker"
billing_mode = "PAY_PER_REQUEST"
hash_key = "IntegrationName"

attribute {
name = "IntegrationName"
type = "S"
}

ttl {
attribute_name = "ExpiresAt"
enabled = true
}

tags = {
Architecture = "Resilience"
Pattern = "CircuitBreaker"
}
}
Uma vez que o repositório centralizado garante a consistência do estado global em milissegundos, como a camada de computação intercepta as requisições de saída para avaliar essa tabela sem contaminar a lógica de domínio puro com abstrações de infraestrutura do banco de dados?

Interceptação Hexagonal e Avaliação de Estado

Interceptamos as requisições de saída implementando um adaptador secundário estrito baseado nos princípios da Arquitetura Hexagonal. O serviço de domínio focado no processamento de pedidos desconhece a existência de regras de resiliência. O adaptador Python, responsável por envolver o cliente HTTP, consulta o DynamoDB imediatamente antes de emitir o pacote de rede. Se a tabela retornar que o circuito está no estado OPEN, o adaptador aborta a operação de rede e levanta uma exceção personalizada (CircuitOpenException) instantaneamente. Esse mecanismo estanca a sangria de recursos. A thread da função Lambda é liberada em menos de dez milissegundos, evitando que a AWS mantenha um contêiner faturável travado aguardando o esgotamento do tempo limite (timeout) de uma API de terceiros que já se provou inoperante.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import time
import boto3
from botocore.exceptions import ClientError
from aws_lambda_powertools import Logger

logger = Logger(service="PaymentAdapter")
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("DistributedCircuitBreaker")

class CircuitOpenException(Exception):
pass

class CircuitBreakerAdapter:
def __init__(self, integration_name: str):
self.integration_name = integration_name

def _is_circuit_closed(self) -> bool:
try:
# Leituras fortemente consistentes garantem o estado global exato
response = table.get_item(
Key={"IntegrationName": self.integration_name},
ConsistentRead=True
)
item = response.get("Item")

if not item:
return True

state = item.get("State", "CLOSED")
expires_at = item.get("ExpiresAt", 0)

# Se está ABERTO mas o tempo expirou, permite sondagem
if state == "OPEN" and time.time() > expires_at:
return self._transition_to_half_open()

return state != "OPEN"
except ClientError as e:
logger.error(f"Falha ao consultar estado do circuito: {str(e)}")
return True # Fail-safe: fecha o circuito se o DDB falhar

def _transition_to_half_open(self) -> bool:
# Lógica de transição omitida para o próximo passo focado em concorrência
return True

def execute_call(self, external_call: callable) -> dict:
if not self._is_circuit_closed():
logger.warning(f"Circuito {self.integration_name} ABERTO. Chamada abortada.")
raise CircuitOpenException("A dependência externa encontra-se inacessível.")

try:
# Executa a chamada HTTP real
return external_call()
except Exception as e:
# Delega o registro da falha
self._record_failure()
raise e
Se o adaptador intercepta e bloqueia chamadas quando o circuito está aberto, como contabilizamos as falhas transientes de milhares de instâncias simultâneas que ainda encontram o circuito fechado sem sofrer condições de corrida que corrompam o limiar matemático de disparo?

Detecção de Falhas via Contadores Atômicos

Contabilizamos as falhas concorrentes utilizando operações de atualização atômica e gravações condicionais nativas da API do Amazon DynamoDB. Quando a chamada HTTP primária retorna um erro 500 (Internal Server Error) ou esgota o tempo limite, o adaptador aciona o método de registro de falha. Se utilizássemos uma abordagem ingênua de ler o contador, incrementar no Python e gravar novamente, milhares de Lambdas concorrentes sobrescreveriam o valor umas das outras, mascarando a severidade do incidente. Empregamos a instrução ADD em uma UpdateExpression para delegar o incremento matemático diretamente ao motor do banco de dados. Simultaneamente, verificamos se o novo valor atinge o limiar configurado (por exemplo, 10 falhas). Se atingir, executamos uma nova atualização condicional alterando o estado para OPEN e injetando um carimbo de tempo UNIX (Epoch) correspondente a 60 segundos no futuro, determinando o tempo exato de resfriamento (Cooldown Period) do circuito.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def _record_failure(self):
try:
# Incremento atômico que retorna o novo valor em uma única transação
response = table.update_item(
Key={"IntegrationName": self.integration_name},
UpdateExpression="ADD FailureCount :inc SET #S = if_not_exists(#S, :closed)",
ExpressionAttributeNames={"#S": "State"},
ExpressionAttributeValues={":inc": 1, ":closed": "CLOSED"},
ReturnValues="UPDATED_NEW"
)

new_count = response.get("Attributes", {}).get("FailureCount", 0)
logger.info(f"Contador de falhas para {self.integration_name} atingiu {new_count}")

if new_count >= 10:
self._trip_circuit()

except ClientError as e:
logger.error(f"Erro atômico ao registrar falha: {str(e)}")

def _trip_circuit(self):
cooldown_seconds = 60
expires_at = int(time.time()) + cooldown_seconds

try:
# Gravação condicional: Só abre se ainda estiver FECHADO ou HALF_OPEN
table.update_item(
Key={"IntegrationName": self.integration_name},
UpdateExpression="SET #S = :open, ExpiresAt = :exp",
ConditionExpression="#S = :closed OR #S = :half_open",
ExpressionAttributeNames={"#S": "State"},
ExpressionAttributeValues={
":open": "OPEN",
":closed": "CLOSED",
":half_open": "HALF_OPEN",
":exp": expires_at
}
)
logger.critical(f"Circuito {self.integration_name} DISPARADO (OPEN).")
except ClientError as e:
if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
pass # Outra thread concorrente já abriu o circuito
else:
raise
Quando o limite de falhas é atingido e o circuito se abre, protegendo o ecossistema e o provedor terceiro, qual mecanismo permite que o sistema identifique autonomamente a estabilização do serviço externo para retomar as operações sem exigir intervenção operacional humana?

Sondagem de Recuperação e o Estado Half-Open

O sistema identifica a estabilização através da transição matemática para o estado semiaberto (Half-Open), orquestrada por algoritmos de controle de concorrência. Após o período de resfriamento de sessenta segundos, a próxima requisição interceptada detectará que o temporizador expirou. Essa thread específica aplica uma atualização condicional no DynamoDB mudando o estado estritamente de OPEN para HALF_OPEN. O uso da expressão de condição (ConditionExpression = "State = :open") é absoluto aqui: garante que apenas uma única função Lambda na frota inteira assuma o papel de batedor (Probe). As requisições concorrentes encontrarão o estado já modificado para HALF_OPEN e continuarão sendo rejeitadas instantaneamente. A thread batedora emite a chamada HTTP real. Se a resposta for um sucesso (HTTP 200), ela reseta o circuito de volta para CLOSED e limpa o contador de falhas. Se a sondagem resultar em um novo erro, o circuito é revertido imediatamente para OPEN com um novo temporizador de resfriamento.

Solução de Problemas Comuns

O obstáculo primordial na implantação de Circuit Breakers orquestrados por banco de dados é a anomalia de latência induzida. Se o cluster do DynamoDB e as funções Lambda operarem em regiões geográficas distintas, o tempo necessário para verificar o estado do circuito (overhead) ultrapassará a latência da própria chamada HTTP protegida. O isolamento deve ocorrer intra-região. Para mitigar o acúmulo residual de tempo em sistemas operando em dezenas de milhares de requisições por segundo (TPS), arquitetos devem encapsular a leitura do estado (_is_circuit_closed) com o Amazon DynamoDB Accelerator (DAX) ou instanciar um pequeno cache na memória local da própria Lambda (variável de escopo global no Python) armazenando o estado durante o breve tempo de vida do ambiente de execução, garantindo avaliações em nanossegundos.
Outro defeito transacional comum reportado no CloudWatch é o excesso de rejeições condicionais ConditionalCheckFailedException preenchendo os logs de erro durante a fase de transição para o HALF_OPEN. Este comportamento não é uma anomalia do banco de dados, mas o funcionamento perfeitamente correto do algoritmo de bloqueio concorrente tentando evitar que múltiplas threads sondem a API simultaneamente. Para limpar os artefatos de log e preservar a observabilidade, o adaptador Python deve capturar essa exceção específica silenciosamente (utilizando blocos except direcionados), traduzindo-a logicamente como um "Circuito Aberto" padrão para o cliente e rejeitando a chamada local sem propagar ruído para as ferramentas de telemetria corporativa.

Conclusão

A externalização das lógicas de controle de falhas para o padrão Circuit Breaker suportado pelo Amazon DynamoDB instaura um paradigma de defesa imunológica no ecossistema de microsserviços. Ao converter esperas síncronas fatais em rejeições controladas e empregar sondagens matemáticas seguras, a arquitetura assegura que as deficiências de fornecedores externos jamais derrubem as operações centrais. À medida que o grau de maturidade da plataforma escala, o gerenciamento do circuito deve evoluir de código de aplicação para políticas de infraestrutura de rede, adotando soluções de Service Mesh como o AWS App Mesh ou VPC Lattice, onde os Sidecars Envoys interceptam e governam o tráfego inter-serviços baseados em regras de roteamento dinâmicas e completamente abstraídas dos desenvolvedores.

Referências

Fowler, M. (2014). Circuit breaker. MartinFowler.com.
Nygard, M. T. (2018). Release it!: Design and deploy production-ready software (2nd ed.). Pragmatic Bookshelf.
Amazon Web Services. (2022). Circuit breaker pattern. AWS Architecture Center. https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/circuit-breaker-pattern.html 
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article