Arquitetando Aplicações Web de Alta Performance: Responsividade, Escalabilidade e Resiliência
Construir uma aplicação web que funciona com dez usuários é trivial. Construir uma que funciona com dez milhões, com latência sub-100ms e disponibilidade de cinco noves, exige uma abordagem arquitetural completamente diferente.
Performance não é feature. É propriedade emergente de decisões arquiteturais acumuladas ao longo de toda a vida do sistema. Sistemas que começam lentos raramente se tornam rápidos através de otimização pontual — eles exigem reaquitetura.
O Modelo Mental: A Equação da Latência
Toda requisição web atravessa uma cadeia de componentes: DNS, TCP handshake, TLS negotiation, balanceador de carga, servidor de aplicação, camada de cache, banco de dados, serviços externos.
A lei de Amdahl aplicada a sistemas web: se 90% do tempo de uma requisição é gasto em consultas ao banco de dados, otimizar os 10% restantes não produz melhoria mensurável.
Estratégia de Cache em Camadas
Cache não é uma tecnologia — é uma arquitetura. Em Java/Kotlin, Caffeine é a implementação de referência para cache em memória:
LoadingCache<String, UserProfile> userCache = Caffeine.newBuilder()
.maximumSize(50_000)
.expireAfterWrite(Duration.ofMinutes(10))
.expireAfterAccess(Duration.ofMinutes(5))
.recordStats()
.build(userId -> userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId)));
O padrão Cache-Aside em Kotlin:
suspend fun getUserProfile(userId: String): UserProfile {
val cacheKey = "user:profile:$userId"
return redisCache.get(cacheKey)?.deserialize()
?: userRepository.findById(userId)
.also { profile ->
redisCache.setEx(cacheKey, Duration.ofMinutes(15), profile.serialize())
}
}
Connection Pooling
HikariCP é o pool de conexões de referência para a JVM. A configuração para serviços de produção de alta carga:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 3000
leak-detection-threshold: 10000
pool-name: PaymentServicePool
Circuit Breaker com Resilience4j
@Bean
fun paymentGatewayCircuitBreaker(): CircuitBreaker =
CircuitBreaker.of("payment-gateway", CircuitBreakerConfig.custom()
.failureRateThreshold(50.0f)
.slowCallDurationThreshold(Duration.ofSeconds(3))
.waitDurationInOpenState(Duration.ofSeconds(30))
.slidingWindowSize(20)
.build())
Observabilidade
Métricas com Micrometer, traces com OpenTelemetry, logs estruturados em JSON:
@Timed(value = "payment.processing", percentiles = [0.5, 0.95, 0.99])
fun processPayment(request: PaymentRequest): PaymentResult {
val span = tracer.nextSpan().name("payment.process")
.tag("payment.method", request.method.name)
.start()
return try {
span.scoped { executePayment(request) }
} catch (e: Exception) {
span.tag("error", e.message ?: "unknown"); throw e
} finally {
span.finish()
}
}
O P99 de latência é mais informativo que a média. A escalabilidade não é conquistada no momento de crise — é projetada no início.