Oficio Código · Prep de entrevista · Addi
La solución del challenge está bien resuelta para lo que piden. Este es el otro lado de la conversación: dónde está su límite real y cómo evoluciona a producción.
En una respiración
Casi todo lo de la derecha sería sobre-ingeniería en el challenge. Ellos piden lo de la izquierda a propósito.
| Challenge | Producción a 100 req/s | |
|---|---|---|
| Interfaz | CLI batch | Servicio HTTP / gRPC |
| Externos | funciones simuladas con latencia | clientes reales, con timeouts y rate limits |
| Persistencia | 1 archivo JSON local (sin DB) | cache in-memory + store distribuido |
| Resiliencia | retry acotado → revisión manual | + circuit breaker, bulkhead, backpressure |
| Concurrencia | 1 lead por vez (checks en paralelo) | miles de leads concurrentes |
El bloque en rojo es el único que no escala. El resto está bien.
%%{init: {'theme':'base','themeVariables':{'fontFamily':'Nunito','primaryColor':'#f1e9ff','primaryTextColor':'#241f3a','primaryBorderColor':'#7c3aed','lineColor':'#6b6b86','secondaryColor':'#eafce0','tertiaryColor':'#fff6d6'}}}%%
flowchart LR
CLI["CLI batch
1 lead por vez"] --> ORQ["Orquestador
virtual threads"]
ORQ -->|paralelo| REG["Registro"]
ORQ -->|paralelo| JUD["Judicial"]
ORQ --> RC["Compliance
cache-first + retry"]
ORQ --> SC["Score > 60"]
RC --> CACHE[("bureau-cache.json
archivo local")]
RC --> BUR["Bureau OFAC"]
style CACHE fill:#ffd9d9,stroke:#c0392b,stroke-width:3px
style BUR fill:#fff3c4,stroke:#a66a00
Fig. 1 — La del challenge. Cache-first hacia el bureau (la dependencia cara): esa parte ya escala.
Primero el compute — que no es el problema. Después el I/O — que sí.
Ley de Little. Con una latencia media por lead de ~400 ms (stage 1 en paralelo ~150 + bureau ~200 + score ~50):
Conclusión: el compute no es el cuello de botella. Está en el I/O.
put() reescribe el mapa entero en cada miss (O(n)), bajo un synchronized global, y en JDK 21 fija el carrier thread de los virtual threads. A 100 req/s: contención brutal.
Si el bureau se pone lento (no caído), cada request paga el presupuesto de reintentos completo. Falta timeout por intento + circuit breaker que corte rápido.
El cache-first reduce la carga al bureau (la dependencia cara/rate-limiteada) al miss rate. Esta decisión de diseño ya escala.
%%{init: {'theme':'base','themeVariables':{'fontFamily':'Nunito','primaryColor':'#ffe3e3','primaryTextColor':'#241f3a','primaryBorderColor':'#c0392b','lineColor':'#6b6b86','actorBkg':'#f1e9ff','actorBorder':'#7c3aed','noteBkgColor':'#fff6d6','noteBorderColor':'#a66a00'}}}%%
sequenceDiagram
participant R as 100 req/s
participant C as FileBureauCache
participant D as disco
R->>C: check (miss) → put
Note over C: synchronized global (serializa todo)
C->>D: reescribe mapa completo + fsync + rename
Note over C,D: O(n) por escritura · pin del carrier thread
R-->>R: los demás requests hacen cola en el lock
Fig. 2 — Aun con 80% de cache-hit, 20 put/s de O(n) bajo un lock global disparan la latencia p99.
No pide sharding ni nada exótico. Pide sacar el cache del archivo y poner el breaker.
%%{init: {'theme':'base','themeVariables':{'fontFamily':'Nunito','primaryColor':'#eafce0','primaryTextColor':'#241f3a','primaryBorderColor':'#3f9c00','lineColor':'#6b6b86','clusterBkg':'#fbfcff','clusterBorder':'#d8d3ee'}}}%%
flowchart TB
LB["Balanceador"] --> S1["Servicio HTTP
virtual threads"]
LB --> S2["Servicio HTTP
virtual threads"]
S1 --> ORQ["Orquestador"]
subgraph NODO["cada nodo"]
direction LR
ORQ --> CAF["Caffeine
cache L1 · TTL"]
ORQ --> CB["Circuit breaker
+ timeout por intento"]
end
CAF -->|miss| REDIS[("Redis · cache L2
compartido · TTL")]
CB --> REG["Registro"]
CB --> JUD["Judicial"]
CB --> BUR["Bureau OFAC"]
REDIS -->|miss| BUR
ORQ --> OBS["Métricas: latencia por check ·
cache hit rate · tasa manual-review"]
style CAF fill:#d4f8d4,stroke:#2e7d32
style REDIS fill:#d4f8d4,stroke:#2e7d32
style CB fill:#d9ecff,stroke:#0a72c4
Fig. 3 — Servicio HTTP + Caffeine (L1) + Redis (L2) + circuit breaker + observabilidad.
| Componente | Challenge | Producción |
|---|---|---|
| Interfaz | CLI batch | Servicio HTTP + virtual threads |
| Cache | archivo JSON, synchronized, O(n) | Caffeine (L1) + Redis (L2), TTL |
| Resiliencia | retry → manual review | + circuit breaker + timeout por intento + bulkhead |
| Externos | funciones simuladas | clientes con rate limiting / backpressure |
| Idempotencia | — | clave por lead (reintentos sin duplicar) |
| Observabilidad | columna ms en consola | métricas + tracing por check |
Casi todo esto ya está listado como pending improvements en el README del repo — lo cual, en la entrevista, se lee como “identifiqué los límites por escrito”, no como “no lo pensé”.
1 nodo JVM + virtual threads → 100 req/s sin despeinarse
(si el cache no es un archivo y
las dependencias responden)
techo real ≠ "Addi a 100 req/s"
techo real = capacidad + rate limits del bureau / registro
── amortiguados por el cache-first ──
100 req/s no pide sharding ni un rediseño. Pide sacar el cache del archivo y poner el circuit breaker.
La frase para la conversación
“La solución del challenge es un kernel de orquestación correcto y probado. A 100 req/s el límite es el cache-en-archivo que el enunciado me pidió sin DB, y la falta de circuit breaker. En producción lo saco a un servicio HTTP con virtual threads, reemplazo el archivo por Caffeine + Redis con TTL, agrego circuit breaker y timeout por intento sobre el bureau, backpressure hacia los externos, idempotencia por lead y observabilidad. El cache-first ya reduce la carga al bureau, que es la dependencia cara — esa parte del diseño ya escala.”