Um provedor de pagamento lento não pode derrubar o checkout
10:00, venda relâmpago. O provedor de pagamento responde em 3 s em vez de 300 ms. Em um minuto o order para de responder qualquer coisa — até o GET /orders/{id}. Por quê?
order
GET /orders/{id}
payment
POST /payments
psp
POST /v1/charges
approved
toxiproxy
toxiproxy:18085
toxiproxy:18095
POST /orders/{id}/pay
CONFIRMED
OrderConfirmed
latency
jitter
timeout
reset_peer
bandwidth
enabled: false
curl -X POST localhost:8474/proxies/payment/toxics \ -d '{"name":"slow","type":"latency","attributes":{"latency":3000}}'
toxicity 0–1: a probabilidade por conexão. POST /reset é o botão de abortar.
toxicity
POST /reset
factory.setConnectTimeout(Duration.ofMillis(500)); factory.setReadTimeout(Duration.ofSeconds(1)); // 0 = forever (the default!)
Os orçamentos encolhem a cada salto: borda 10 s > gateway 5 s > order→payment 1 s × tentativas.
max-attempts: 3 wait-duration: 200ms enable-exponential-backoff: true # 200 ms, 400 ms enable-randomized-wait: true randomized-wait-factor: 0.5 # ± 50% jitter retry-exceptions: [ResourceAccessException, HttpServerErrorException]
Idempotency-Key: order-<id>
UNIQUE
200
Um timeout não significa que a chamada falhou — a resposta pode ter se perdido. A chave torna o retry inofensivo.
Acompanhe: /actuator/circuitbreakers, /actuator/circuitbreakerevents.
/actuator/circuitbreakers
/actuator/circuitbreakerevents
As chamadas dão certo em 700 ms — abaixo do timeout e do limite de chamada lenta. A 70/s, o GET /orders/{id} fica lento mesmo assim. Por quê, e o que resolve?
max-concurrent-calls: 10
max-wait-duration: 0
503
@Retry(name = "payment", fallbackMethod = "unavailable") @CircuitBreaker(name = "payment") @Bulkhead(name = "payment") public PaymentResponse charge(UUID orderId, BigDecimal amount) { ... }
CallNotPermittedException
BulkheadFullException
pay()
UPDATE … WHERE status = 'PENDING_PAYMENT'
pay
browse
pay_fast_fail
k6 load/pay-under-chaos.js: pay (usa o payment) e browse (não usa) ao mesmo tempo — o segundo revela a cascata.
load/pay-under-chaos.js
Sem hipótese, não há experimento. Uma hipótese refutada é o resultado valioso.
cascading-failure
retry-storm
exp+jitter
O simulador mostra a forma; k6 + Toxiproxy dão os seus próprios números.
Roteiro: (1) a cascata; (2) payment, psp, Toxiproxy; (3) timeout; (4) retry + idempotência; (5) circuit breaker; (6) bulkhead; (7) game day; (8) medir.