Compraste la CPU «más rápida». Relojes más altos, microarquitectura nueva, una hoja de especificaciones llena de bravatas.
Y la latencia en producción empeoró.
Eso no es mala suerte. Es física, más un montón de suposiciones optimistas sobre lo que significa «más rápida».
En sistemas reales, la CPU normalmente no espera a sí misma. Está esperando datos. Cuando los datos no están en caché,
la CPU se convierte en un costoso calefactor que de vez en cuando hace trabajo.
Qué significa realmente «monstruo de caché»
«Monstruo de caché» no es un insulto. Es un cumplido para un sistema (o un chip) que gana manteniendo el conjunto de trabajo
cerca de los núcleos. El monstruo puede ser:
- Una CPU con una gran caché de último nivel (LLC/L3), ancho de banda alto y un buen prefetcher.
- Una plataforma con canales de memoria rápidos y topología NUMA sensata.
- Una aplicación que se comporta como si respetara la localidad (y no lanza punteros como confeti).
- Una pila de almacenamiento con cachés agresivos (page cache, buffer pool de base de datos, ZFS ARC, Redis, CDN).
El truco es que «CPU más rápida» a menudo significa «puede retirar más instrucciones por segundo si los datos ya están allí«.
Si el sistema sigue fallando en cachés, no estás limitado por el pico de cómputo. Estás limitado por la velocidad de obtención.
Y obtener datos, en una máquina moderna, es un proceso burocrático de varias etapas.
Piénsalo como una cocina. Tu chef puede cortar más rápido (mayor IPC, mayor reloj), claro. Pero si los ingredientes
aún están en un camión en algún lugar (fallo de caché), la cocina está muy silenciosa.
La jerarquía de memoria: donde el tiempo va a morir
La latencia es la verdadera moneda
La gente de sistemas aprende esto de la forma difícil: el rendimiento agregado hace titulares, la latencia escribe tus informes de incidente.
Un solo fallo de caché puede bloquear un núcleo el tiempo suficiente para hacer irrelevante tu CPU «más rápida».
Esto no es teórico; por eso la ingeniería de rendimiento es mayormente el estudio de la espera.
Aproximadamente, tratas con una escalera de «cerca» a «lejos»:
- Registros: básicamente inmediatos.
- Caché L1: diminuta y extremadamente rápida.
- Caché L2: más grande, aún rápida.
- L3/LLC: compartida, más grande, más lenta.
- DRAM: mucho más lenta y compartida entre muchos núcleos hambrientos.
- Memoria de nodo NUMA remoto: aún más lenta.
- SSD/NVMe: órdenes de magnitud más lentas que la DRAM.
- Almacenamiento en red: ahora estás negociando con las leyes de la geografía.
El trabajo de la CPU es ejecutar instrucciones. Tu trabajo es mantener esas instrucciones alimentadas con datos desde el lugar
más cercano posible de esa escalera. Porque cada peldaño que bajas te cuesta ciclos detenidos, cola de latencia estropeada,
y gráficos de «utilización de CPU» que parecen estar bien hasta que los usuarios empiezan a gritar.
Las cachés no son solo «RAM pequeña»
Las cachés se construyen alrededor de la localidad:
- Localidad temporal: si lo usaste, puede que lo vuelvas a usar pronto.
- Localidad espacial: si usaste esta dirección, puede que uses direcciones cercanas pronto.
Las cachés de hardware operan con líneas de caché (comúnmente 64 bytes). Eso significa que no recuperas un entero;
recuperas también a sus vecinos, los quieras o no.
Por eso «CPU más rápida» suele ser derrotada por «mejor diseño». Una CPU ligeramente más lenta que consigue aciertos en caché puede
vencer a una más rápida que destroza la LLC y pasa la vida en viajes de ida y vuelta a DRAM.
La matemática cruel de los fallos
Una CPU puede retirar múltiples instrucciones por ciclo. La ejecución fuera de orden moderna oculta algo de latencia,
pero solo hasta el punto en que el núcleo encuentra trabajo independiente. Cuando tu carga de trabajo hace chase de punteros,
tiene muchas ramas o está serializada (común en bases de datos, asignadores y muchos runtimes de alto nivel), la CPU se queda sin trabajo independiente
rápidamente. Entonces te bloqueas. En serio.
También es por esto que la «latencia media» puede engañar. Un fallo de caché no solo añade un poco de tiempo; puede convertir
una solicitud en un caso extremo de cola larga. Los usuarios no abren tickets por el p50. Abren tickets por «se colgó y luego actualizó».
Por qué la CPU más rápida pierde en producción
1) Tu carga no está limitada por cómputo
Muchas cargas en producción están limitadas por datos:
- Bases de datos leyendo índices y siguiendo punteros a través de B-trees.
- Consultas clave/valor con patrones de acceso aleatorio.
- Microservicios que realizan parseo JSON, asignaciones, búsquedas en hashmap y registro de logs.
- Canales de observabilidad que comprimen, agrupan y envían eventos con metadatos pesados.
La CPU no está «ocupada» haciendo aritmética. Está ocupada esperando memoria, bloqueos, la finalización de E/S, la propiedad de líneas de caché,
a que el asignador encuentre espacio libre, o a que el recolector de basura detenga el mundo y limpie.
2) Las CPU «más rápidas» a menudo intercambian caché por núcleos o reloj
Las SKUs de producto hacen esto constantemente. Verás CPUs con:
- Más núcleos pero menos caché por núcleo.
- Relojes de boost más altos pero límites de potencia más estrictos (así que bajo carga sostenida baja la frecuencia).
- Diferente topología de caché (slices de LLC compartidas, distinto comportamiento del interconector).
Si tu app necesita caché, entonces una CPU con menos núcleos pero más LLC puede rendir mejor que una CPU «más grande» que fuerza
tu conjunto de trabajo hacia DRAM. Ese es el monstruo de caché: no es glamoroso, solo efectivo.
3) NUMA y la topología son multiplicadores (y destructores) de rendimiento
NUMA no es una nota académica. Es la razón por la que tu nuevo servidor es «misteriosamente más lento» que el antiguo.
Si los hilos se ejecutan en un socket pero asignan memoria en otro, pagas una penalización por acceso remoto en casi cada fallo.
Ahora tu núcleo de CPU «más rápido» está sorbiendo datos a través de una pajita más larga.
La topología también afecta la contención de caché compartida. Coloca demasiados vecinos ruidosos en el mismo dominio de LLC y
tu tasa de aciertos colapsa. La CPU es rápida. Solo que tiene hambre.
4) El almacenamiento es el fallo de caché definitivo
Cuando fallas en memoria y aterrizas en almacenamiento, ya no comparas relojes. Estás comparando microsegundos y milisegundos.
Aquí es donde los monstruos de caché muestran los dientes: page cache, buffer pools de bases de datos, ZFS ARC, caches de objetos, edges de CDN.
Un acierto de caché puede convertir «necesita una lectura de disco» en «devuelve inmediatamente». Eso no es un 10% de mejora. Es supervivencia.
5) La latencia de cola larga castiga a los impacientes
Las CPU más rápidas ayudan al p50 cuando estás limitado por cómputo. La caché ayuda al p99 cuando estás limitado por datos.
Producción es mayormente un negocio de p99. Tu rotación de on-call ciertamente lo es.
Exactamente una cita, porque los ingenieros merecen al menos una buena:
Todos tienen un entorno de pruebas. Algunos tienen la suerte de tener un entorno de producción separado.
—Aforismo anónimo de operaciones
Broma #1: Comprar una CPU más rápida para arreglar fallos de caché es como comprar un coche más rápido porque siempre olvidas dónde lo estacionaste.
Hechos interesantes y contexto histórico
-
La «pared de memoria» se nombró como problema a mediados de los 90: las velocidades de CPU mejoraron más rápido que la latencia de DRAM,
obligando a los arquitectos a apoyarse fuertemente en cachés y prefetching. -
Las primeras CPUs no tenían caché en chip: las cachés se movieron on-die a medida que el presupuesto de transistores creció, porque la latencia
de caché fuera de chip era demasiado cara. -
Las líneas de caché existen porque las transferencias de memoria son por bloques: recuperar 64 bytes amortiza la sobrecarga del bus, pero
también significa que puedes desperdiciar ancho de banda arrastrando vecinos inútiles. -
La asociatividad es un compromiso: mayor asociatividad reduce fallos por conflicto pero cuesta más potencia y complejidad.
Los chips reales hacen compromisos pragmáticos que aparecen como acantilados de rendimiento «misteriosos». -
Las políticas de LLC inclusiva vs no inclusiva importan: algunos diseños mantienen líneas de caché de niveles superiores duplicadas en la LLC,
lo que afecta el comportamiento de expulsión y la capacidad efectiva de caché. -
Las CPUs modernas usan prefetchers sofisticados: adivinan accesos de memoria futuros. Cuando aciertan, pareces un genio. Cuando fallan, pueden
contaminar cachés y consumir ancho de banda. -
NUMA se volvió común conforme escalaron servidores multi-socket: el acceso a memoria local es más rápido que el remoto. Ignorarlo es la forma más rápida
de convertir «más sockets» en «más tristeza». -
En Linux, el page cache es una característica de rendimiento de primera clase: por eso una segunda ejecución de un trabajo puede ser
dramáticamente más rápida que la primera—a menos que la evites con Direct I/O. -
El almacenamiento pasó de HDD a SSD a NVMe: la latencia bajó enormemente, pero sigue siendo mucho más lenta que la DRAM.
Un acierto de caché sigue siendo otro universo distinto a una lectura de almacenamiento.
Modos de fallo: cómo la caché y la E/S te muerden
Fallos de caché que parecen «problemas de CPU»
La trampa clásica es ver uso alto de CPU y asumir que necesitas más CPU. A veces sí. Con frecuencia no.
CPU alta puede significar «ejecutando instrucciones activamente» o «spin, bloqueado, esperando memoria».
Ambos aparecen como «CPU ocupada» para el ojo inexperto.
Los fallos de caché se manifiestan como:
- Menor instrucciones por ciclo (IPC) a pesar de alta utilización de CPU.
- Tasas de fallo de LLC más altas bajo carga.
- Rendimiento que deja de escalar con más núcleos.
- Picos de latencia cuando el conjunto de trabajo cruza un límite de caché.
Falsa compartición y ping-pong de líneas de caché
Puedes tener mucha caché y aun así perder porque los núcleos pelean por líneas de caché. Cuando dos hilos en diferentes núcleos
escriben variables distintas que comparten la misma línea de caché, la línea rebota entre núcleos para mantener coherencia.
Felicidades: inventaste un sistema distribuido dentro de tu CPU.
Fallos de TLB: el fallo de caché furtivo
Las Translation Lookaside Buffers (TLB) cachean las traducciones virtual→físico. Cuando hay un fallo allí, pagas la sobrecarga de la caminata de páginas
antes incluso de recuperar los datos. Cargas con espacios de direcciones enormes, acceso aleatorio o heaps fragmentados pueden convertir la presión de TLB en latencia.
Fallos de caché de almacenamiento: page cache, buffer pools y amplificación de lectura
Los fallos del page cache se convierten en lecturas de disco. Las lecturas de disco se convierten en latencia. Y si tu capa de almacenamiento hace amplificación de lectura
(común en algunos sistemas de ficheros copy-on-write, capas cifradas o E/S mal alineada), puedes «fallar» más de una vez por solicitud.
Muchas «actualizaciones de CPU» fallan porque la ruta de almacenamiento es el cuello de botella, y la CPU solo pasa más tiempo esperando más rápido.
Broma #2: La forma más rápida de hacer un sistema más lento es optimizarlo hasta que empiece a hacer trabajo «útil» que no pediste.
Guía rápida de diagnóstico
Cuando el rendimiento retrocede tras una actualización de CPU (o cualquier «mejora de hardware»), no adivines. Trabaja la pila de arriba a abajo
y fuerza al sistema a confesar dónde está esperando.
Primero: decide si estás limitado por cómputo, memoria o E/S
- Limitado por cómputo: IPC alto, CPU de usuario alto, pocos ciclos detenidos, el escalado mejora con más núcleos.
- Limitado por memoria: IPC bajo, altas tasas de fallo en caché/TLB, muchos ciclos detenidos, el escalado se estanca pronto.
- Limitado por E/S: aparece iowait, latencia de almacenamiento alta, hilos bloqueados en lecturas, picos de faltas de page cache.
Segundo: revisa el comportamiento de caché e IPC bajo carga real
- Usa
perfpara ver ciclos, instrucciones, fallos de caché, ciclos detenidos. - Observa fallos de LLC y contadores de ancho de banda de memoria si están disponibles.
- Busca acantilados súbitos cuando aumenta QPS (el conjunto de trabajo excede la caché).
Tercero: verifica la localidad NUMA y el comportamiento de frecuencia de CPU
- Confirma que la carga está fijada sensatamente o al menos no esté peleando contra el planificador.
- Comprueba si las asignaciones de memoria son locales a la CPU que ejecuta el hilo.
- Verifica que no estés siendo limitado térmica/potencia bajo carga sostenida.
Cuarto: sigue el fallo de caché hasta la pila de almacenamiento
- Revisa señales de aciertos/fallos del page cache (readahead, fallos mayores).
- Mide la distribución de latencias de almacenamiento, no solo el throughput.
- Confirma profundidad de cola, planificador y comportamiento del sistema de ficheros (especialmente capas CoW/RAID).
Quinto: solo entonces considera cambiar la CPU
Si estás limitado por cómputo, claro: compra CPU. Si estás limitado por memoria o E/S, arregla la localidad, el cache y el layout primero.
Es más barato y generalmente más efectivo.
Tareas prácticas: comandos, salidas, qué significan y la decisión que tomas
Estos son chequeos de grado producción. Ejecútalos durante una prueba de carga o una ventana de incidente real (con cuidado). Cada tarea incluye:
el comando, una salida representativa, qué significa y qué decides después.
Task 1: Check CPU frequency and throttling behavior
cr0x@server:~$ lscpu | egrep 'Model name|CPU\(s\)|Socket|Thread|NUMA|MHz'
Model name: Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz
CPU(s): 64
Thread(s) per core: 2
Socket(s): 2
NUMA node(s): 2
CPU MHz: 1199.842
Qué significa: Si MHz está muy por debajo del base/esperado bajo carga, puedes estar limitado por potencia/temperatura o en un governor conservador.
Decisión: Verifica el governor y la frecuencia sostenida bajo carga; no asumas que los relojes de boost son reales en producción.
Task 2: Confirm CPU governor (performance vs powersave)
cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
powersave
Qué significa: «powersave» puede limitar la frecuencia y aumentar la latencia bajo cargas intermitentes.
Decisión: Si este es un nodo sensible a latencia, cambia a «performance» (o ajusta la política de plataforma) durante las pruebas y compara.
Task 3: Quick view of run queue pressure and iowait
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
8 0 0 242112 81240 912340 0 0 0 12 6200 9800 62 10 22 6 0
10 1 0 240980 81240 910120 0 0 64 980 6900 11200 55 11 18 16 0
11 2 0 239500 81240 908400 0 0 128 1400 7200 11800 48 12 16 24 0
Qué significa: Alto r sugiere contención de CPU; alto wa y b sugiere esperas de I/O. Esta muestra muestra iowait en ascenso.
Decisión: Si iowait sube con la carga, no pierdas tiempo en upgrades de CPU—sigue la ruta de I/O.
Task 4: Identify blocked tasks (often I/O or lock contention)
cr0x@server:~$ ps -eo state,pid,comm,wchan:32 | awk '$1 ~ /D/ {print}'
D 18423 postgres io_schedule
D 19011 java futex_wait_queue_me
Qué significa: El estado D indica sueño no interrumpible, comúnmente esperas de I/O (io_schedule) o a veces rutas de bloqueo del kernel.
Decisión: Si muchos hilos están en D, la latencia de almacenamiento o la contención del sistema de ficheros son sospechosos principales.
Task 5: Measure cache misses and IPC with perf stat (quick, high value)
cr0x@server:~$ sudo perf stat -a -e cycles,instructions,cache-references,cache-misses,branches,branch-misses -I 1000 -- sleep 3
# time counts unit events
1.000353504 12,804,112,210 cycles
1.000353504 6,210,554,991 instructions # 0.49 insn per cycle
1.000353504 401,220,118 cache-references
1.000353504 121,884,332 cache-misses # 30.37% of all cache refs
1.000353504 1,002,884,910 branches
1.000353504 21,880,441 branch-misses # 2.18% of all branches
Qué significa: IPC ~0.49 y una alta ratio de fallos de caché gritan bloqueos por memoria. Una CPU «más rápida» no solucionará esto a menos que tenga un comportamiento de caché/memoria significativamente mejor.
Decisión: Reduce el conjunto de trabajo, mejora la localidad, fija hilos/memoria para NUMA, o cambia estructuras de datos antes de comprar cómputo.
Task 6: Check NUMA placement and remote memory usage (if numactl is available)
cr0x@server:~$ numastat -p 18423
Per-node process memory usage (in MBs) for PID 18423 (postgres)
Node 0 8123.45
Node 1 421.12
Total 8544.57
Qué significa: La memoria está mayormente en el Nodo 0. Si los hilos corren en el Nodo 1, pagarás penalizaciones por acceso remoto.
Decisión: Alinea afinidad de CPU y política de memoria: fija procesos o arregla la orquestación para que asignación y ejecución coexistan.
Task 7: See where threads are running (CPU affinity and migration clues)
cr0x@server:~$ ps -eLo pid,tid,psr,comm | awk '$4=="postgres"{print $0}' | head
18423 18423 12 postgres
18423 18424 3 postgres
18423 18425 47 postgres
18423 18426 19 postgres
Qué significa: Hilos dispersos por CPUs puede estar bien, o puede ser hostil a la caché si la carga comparte estructuras calientes y hace rebotar líneas de caché.
Decisión: Si ves contención y escalado pobre, prueba fijar a un subconjunto de núcleos o a un único nodo NUMA y mide p99.
Task 8: Check major page faults (page cache misses that hit storage)
cr0x@server:~$ pidstat -r -p 19011 1 3
Linux 6.2.0 (server) 01/10/2026 _x86_64_ (64 CPU)
12:00:01 UID PID minflt/s majflt/s VSZ RSS %MEM Command
12:00:02 1001 19011 1200.00 85.00 12488192 2380040 7.42 java
12:00:03 1001 19011 1180.00 92.00 12488192 2381208 7.42 java
Qué significa: majflt/s indica fallos mayores que requieren I/O de disco (a menos que ya estén cacheados). Eso suele ser un asesino de latencia.
Decisión: Incrementa memoria, reduce churn de archivos, o asegura que archivos calientes queden en caché; considera caché a nivel de aplicación.
Task 9: Measure storage device latency and queueing
cr0x@server:~$ iostat -x 1 3
Linux 6.2.0 (server) 01/10/2026 _x86_64_ (64 CPU)
Device r/s w/s r_await w_await aqu-sz %util
nvme0n1 820.0 120.0 6.10 8.40 5.40 98.0
Qué significa: r_await/w_await son latencias medias; aqu-sz y alto %util indican colas y saturación.
Decisión: Si el dispositivo está saturado, reduce I/O (cachea más), reparte la carga entre dispositivos o cambia el patrón de acceso antes de tocar la CPU.
Task 10: Identify hot files and I/O sources (quick and dirty)
cr0x@server:~$ sudo lsof -nP | awk '{print $1,$2,$4,$9}' | egrep 'postgres|java' | head
postgres 18423 mem /usr/lib/x86_64-linux-gnu/libssl.so.3
postgres 18423 12u /var/lib/postgresql/15/main/base/16384/2619
java 19011 45u /var/log/app/service.log
Qué significa: Muestra qué archivos están abiertos. Si ves ficheros de logs o rutas inesperadas dominando, tu «problema de cómputo» puede ser «registro a muerte».
Decisión: Reduce logging sincrónico, agrupa escrituras, mueve logs a un volumen más rápido o ajusta el nivel de logging.
Task 11: Check filesystem and mount options (Direct I/O, barriers, atime)
cr0x@server:~$ findmnt -no TARGET,SOURCE,FSTYPE,OPTIONS /var/lib/postgresql
/var/lib/postgresql /dev/md0 ext4 rw,relatime,data=ordered
Qué significa: Opciones como relatime importan; otras (modos de journal, barreras) afectan la latencia y la seguridad.
Decisión: Tunea solo con una razón clara. Si cambias opciones relacionadas con la seguridad para perseguir benchmarks, escribe el postmortem por adelantado.
Task 12: Observe page cache pressure and reclaim behavior
cr0x@server:~$ grep -E 'pgscan|pgsteal|pgfault|pgmajfault' /proc/vmstat | head -n 8
pgfault 1283394021
pgmajfault 228103
pgscan_kswapd 901223
pgscan_direct 188004
pgsteal_kswapd 720111
pgsteal_direct 141220
Qué significa: Un pgscan_direct creciente sugiere reclaim directo (procesos reclamando memoria por sí mismos), lo que puede dañar la latencia.
Decisión: Si el reclaim directo es alto durante la carga, reduce la presión de memoria: añade RAM, reduce churn de caché, ajusta límites de memoria, o corrige heaps sobredimensionados.
Task 13: Check swap activity (a slow-motion disaster)
cr0x@server:~$ swapon --show
NAME TYPE SIZE USED PRIO
/dev/sda3 partition 16G 2G -2
Qué significa: El uso de swap no es automáticamente malvado, pero hacer swap sostenido bajo carga es una fábrica de fallos de caché con pasos extra.
Decisión: Si la latencia importa, evita el thrash de swap: limita memoria, corrige fugas, ajusta heaps o añade memoria.
Task 14: Check memory bandwidth pressure (top-level hint via perf)
cr0x@server:~$ sudo perf stat -a -e stalled-cycles-frontend,stalled-cycles-backend,LLC-loads,LLC-load-misses -I 1000 -- sleep 3
# time counts unit events
1.000339812 3,110,224,112 stalled-cycles-frontend
1.000339812 8,901,884,900 stalled-cycles-backend
1.000339812 220,112,003 LLC-loads
1.000339812 88,440,120 LLC-load-misses # 40.17% of all LLC loads
Qué significa: Stalls en backend y altas tasas de fallo en LLC sugieren fuertemente límites de latencia/ancho de banda de memoria.
Decisión: Necesitas mejoras de localidad, no GHz en bruto. Considera cambios en estructuras de datos, sharding o co-ubicación de datos calientes.
Task 15: Check ZFS ARC effectiveness (if on ZFS)
cr0x@server:~$ arcstat 1 3
time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c
12:00:01 12K 2.8K 23 920 33 1.7K 61 180 6 96G 112G
12:00:02 13K 4.9K 37 2.1K 43 2.6K 53 200 4 96G 112G
Qué significa: Un porcentaje de miss en ARC creciente bajo carga implica que tu conjunto de trabajo no cabe en ARC; las lecturas pasan al disco.
Decisión: Añade RAM, ajusta límites de ARC, añade caché secundaria más rápida si procede, o reduce el conjunto de trabajo (bloat de índices, datos fríos).
Task 16: Validate network latency as “remote cache miss” (service calls)
cr0x@server:~$ ss -tin dst 10.20.0.15:5432 | head -n 12
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 10.20.0.10:44912 10.20.0.15:5432
cubic wscale:7,7 rto:204 rtt:1.8/0.4 ato:40 mss:1448 cwnd:10 bytes_acked:1234567 bytes_received:2345678
Qué significa: RTT te dice si tu «consulta lenta» es en realidad «jitter de red». En sistemas distribuidos, la red es solo otro nivel de caché—uno caro.
Decisión: Si RTT/jitter es alto, enfócate en co-ubicación, pooling de conexiones o cachear resultados más cerca del llamador.
Tres micro-historias corporativas del frente
Micro-historia 1: El incidente causado por una suposición errónea (la «actualización de CPU» que empeoró el p99)
Una empresa mediana gestionaba una API de búsqueda intensiva. El equipo tenía una historia clara: las consultas son lentas, la CPU está alta, compremos CPUs más rápidas.
Compras entregaron servidores nuevos con relojes más altos y más núcleos. Los benchmarks en un solo nodo parecían decentes.
La implementación comenzó. En pocas horas, la latencia p99 se disparó y los presupuestos de errores empezaron a evaporarse.
La suposición errónea fue sutil: asumieron que «más CPU» mejora una carga ya limitada por la localidad de memoria.
Los servidores nuevos tenían más núcleos por socket, pero menos LLC por núcleo y características NUMA ligeramente diferentes.
Bajo carga multi-inquilino real, el conjunto de trabajo ya no cabía bien en caché. La tasa de fallos de LLC subió.
El IPC cayó. Los gráficos de CPU aún parecían «ocupados», lo que engañó a todos más tiempo del necesario.
La respuesta al incidente tomó un giro incómodo cuando los ingenieros se dieron cuenta de que su flota «más rápida» se había vuelto mejor en
ejecutar bloqueos. Revirtieron el tráfico, luego reprodujeron la regresión en una prueba controlada con contadores perf.
La prueba evidente fue un aumento consistente en LLC load misses y stalls de backend bajo la misma QPS.
La solución no fue deshacer la compra de hardware (ese barco ya había zarpado), sino cambiar la colocación y reducir la contención entre núcleos.
Fijaron el servicio a menos núcleos por nodo NUMA y ejecutaron más réplicas en lugar de intentar «usar todos los núcleos».
Contraintuitivo, pero recuperó el p99. Más tarde rehicieron estructuras de datos para ser más amigables con la caché y redujeron el pointer chasing.
El elemento del postmortem que importó: las pruebas de aceptación de rendimiento deben incluir métricas de fallos de caché/IPC, no solo throughput.
«Utilización de CPU» por sí sola es una piedra de humor, no un diagnóstico.
Micro-historia 2: La optimización que salió mal (Direct I/O: el page cache no era el villano)
Otra empresa ejecutaba una pipeline que ingería eventos, los escribía a disco y los compactaba periódicamente.
Vieron presión de memoria y decidieron que el page cache de Linux estaba «robando» RAM a la aplicación.
Un ingeniero cambió una opción: usar Direct I/O para escrituras y lecturas para «evitar contaminar la caché».
Los gráficos se vieron bien un día. El uso de memoria se estabilizó. Luego empezaron las alarmas de latencia.
No en todos lados—justo en el peor lugar posible: los jobs de compactación y ciertos caminos de lectura que dependían de re-leer
datos recientes. Con Direct I/O, esas lecturas eludían por completo el page cache. Cada re-lectura se convirtió en una operación de almacenamiento.
NVMe es rápido, pero no «tan rápido como RAM haciéndose pasar por disco».
La jugada salió mal con más metralla. Sin el amortiguamiento del page cache, la E/S se volvió más explosiva.
La profundidad de cola se disparó. Las latencias de cola se dispararon también. La CPU seguía sin ser el cuello de botella; simplemente pasó más tiempo en iowait.
El equipo había optimizado los gráficos de memoria a costa de la latencia visible al cliente.
La solución eventual fue aburrida: revertir Direct I/O para los caminos de lectura calientes, mantenerlo solo donde los datos fueran verdaderamente fríos o secuenciales,
y establecer límites de cgroup razonables para que el page cache no pudiera asfixiar el proceso.
También introdujeron una pequeña caché a nivel de aplicación para metadatos y evitar lecturas aleatorias repetidas.
Lección aprendida: «evitar cachés» no es una estrategia de rendimiento. Es un arma. Úsala solo cuando entiendas qué va a impactar.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día (medir conjunto de trabajo y planear caché)
Una firma financiera tenía una carga híbrida batch + API. Planeaban una renovación: generación de CPU más nueva, misma RAM,
mismo almacenamiento. El equipo SRE insistió en un perfil pre-migración: medir tamaño del conjunto de trabajo, tasas de aciertos de caché y comportamiento NUMA
durante la semana pico, no solo la hora pico.
Fue impopular porque retrasó el proyecto. Requirió capturar estadísticas perf, tasas de fallos de página y histogramas de latencia de almacenamiento.
También requirió una prueba de carga que se pareciera a producción, no al folleto del benchmark.
El equipo insistió, porque ya les habían quemado antes.
El perfil mostró un acantilado previsible: una vez que el dataset crecía más allá de cierto punto, la tasa de aciertos del buffer cache caía y las lecturas
golpeaban disco. El p99 explotaba. El sistema actual sobrevivía porque tenía ligeramente más caché efectiva (buffer pool de la BD más cache OS) que la configuración planeada.
La CPU «más rápida» no iba a importar una vez que los fallos de caché empezaran a aterrizar en disco.
Ajustaron el plan de refresco: más RAM por nodo, ligeramente menos núcleos y una estrategia clara de colocación NUMA.
También programaron una rutina para mantener las particiones más calientes residentes en caché durante horario de negocio.
El día del lanzamiento fue sin incidentes, que es el mayor cumplido en operaciones.
Lección: la medición aburrida vence al reemplazo emocionante. Los mejores incidentes son los que nunca ocurren.
Errores comunes (síntomas → causa raíz → arreglo)
1) Síntoma: CPU alta, throughput plano
Causa raíz: Carga limitada por memoria; IPC bajo debido a fallos de caché o stalls de backend.
Arreglo: Usa perf para confirmar IPC bajo y fallos de LLC altos. Reduce el conjunto de trabajo, mejora la localidad de datos, shardea o elige una CPU con más caché por núcleo.
2) Síntoma: p99 empeoró tras despliegue de «CPU más rápida»
Causa raíz: Cambió la topología de caché/LLC por núcleo; cambió la colocación NUMA; difiere el prefetch; mayor contención por socket.
Arreglo: Compara contadores perf antiguo vs nuevo bajo carga idéntica. Fija a nodos NUMA, reduce compartición de núcleos, reevalúa el dimensionamiento y la colocación de instancias.
3) Síntoma: Mucho iowait, pero los discos no parecen «ocupados» por throughput
Causa raíz: Saturación por latencia: E/S aleatoria pequeña, alta cola, o latencia tail en almacenamiento; el throughput lo oculta.
Arreglo: Usa iostat -x y observa await y aqu-sz. Reduce lecturas aleatorias mediante caché, arregla patrones de consulta/índice o añade capacidad de IOPS.
4) Síntoma: El rendimiento empeora al añadir núcleos
Causa raíz: Contención de caché compartida, contención de locks, falsa compartición o saturación de ancho de banda de memoria.
Arreglo: Escala horizontal en lugar de vertical; fija hilos; elimina falsa compartición; reduce estado mutable compartido; mide con perf y flame graphs si es posible.
5) Síntoma: «Optimización» reduce uso de memoria pero aumenta latencia
Causa raíz: Eludir cachés (Direct I/O), reducir buffer pools o evicción agresiva lleva a lecturas de almacenamiento.
Arreglo: Restaura caché para caminos calientes, ajusta memoria correctamente y mide ratios de acierto de caché y fallos mayores.
6) Síntoma: Picos periódicos de latencia cada pocos segundos/minutos
Causa raíz: Pausas de GC, ciclos de expulsión de caché, compactación o reclaim de fondo (kswapd/reclaim directo).
Arreglo: Revisa majflt, señales de reclaim en vmstat y logs de GC. Reduce churn de asignación, ajusta el heap, añade memoria o suaviza la programación de compactación.
7) Síntoma: Un host es más lento que sus hermanos idénticos
Causa raíz: Ajustes BIOS diferentes, política de potencia, microcódigo, población de memoria (canales), o vecino ruidoso saturando LLC/ancho de banda.
Arreglo: Compara lscpu, governors, NUMA y estadísticas perf entre hosts. Estandariza firmware y tunables del kernel; aisla cargas ruidosas.
8) Síntoma: Alto CPU del sistema, bajo CPU de usuario, «nada» en perfiles de app
Causa raíz: Overhead del kernel: fallos de página, presión del stack de red, cambios de contexto o churn de metadata del sistema de ficheros.
Arreglo: Usa vmstat, pidstat, perf top. Reduce syscalls (batching), ajusta logging, arregla churn de ficheros y elimina puntos de sincronización accidentales.
Listas de verificación / plan paso a paso
Paso a paso: decidir si la caché vence a la CPU para tu carga
- Recopila un perfil parecido a producción: mezcla de QPS, concurrencia, tamaño del dataset y latencias p50/p95/p99.
- Mide IPC y fallos de caché bajo carga: perf stat (cycles, instructions, LLC misses, stalled cycles).
- Mide la distribución de latencia de almacenamiento: iostat -x y tiempos a nivel de aplicación.
- Mide fallos mayores: pidstat -r y /proc/vmstat; confirma si las lecturas golpean disco.
- Valida la colocación NUMA: numastat, colocación de hilos y política de afinidad.
- Comprueba el escalado: ejecuta con 1, 2, 4, 8, N núcleos y ve si el throughput escala o se estanca.
- Cambia una cosa a la vez: fijado, tamaño de buffer pool, tamaño de caché, layout de datos; luego vuelve a probar.
- Solo entonces decide el hardware: más caché por núcleo, más RAM, memoria más rápida o más nodos.
Lista operativa: antes de un despliegue de «upgrade» de CPU
- Baseline de contadores perf (IPC, LLC misses, stalls) en el hardware antiguo.
- Confirma ajustes de BIOS: política de potencia/rendimiento, SMT, interleaving de memoria.
- Confirma paridad de kernel y microcódigo entre antiguo y nuevo.
- Ejecuta una prueba de carga con el tamaño real del dataset y sesgo de acceso realista.
- Compara latencia tail, no solo throughput.
- Planifica una reversión que no requiera una reunión.
Lista de almacenamiento/caché: hacer que la caché trabaje para ti
- Conoce el tamaño de tu conjunto de trabajo (datos calientes) y compáralo con RAM/buffer caches.
- Prefiere acceso secuencial cuando sea posible; la E/S aleatoria es un impuesto.
- Mantén índices y metadata caliente en memoria; el disco es para la verdad fría, no para opiniones calientes.
- Mide ratios de acierto de caché y tasas de expulsión, no lo adivines.
- No «optimices» deshabilitando seguridad (barreras de escritura, journaling) a menos que estés dispuesto a asumir la historia de pérdida de datos.
Preguntas frecuentes (FAQ)
1) ¿Realmente la caché de CPU es tan importante, o es solo drama de fanáticos del rendimiento?
Es importante. Muchas cargas reales están limitadas por la latencia de memoria. Un pequeño cambio en la tasa de fallo de LLC puede afectar el p99
mucho más que un modest aumento de frecuencia de CPU.
2) Si mi uso de CPU es 90%, ¿no prueba eso que estoy limitado por CPU?
No. El uso alto de CPU puede incluir ciclos detenidos, bucles de espera activa, contención de locks y overhead del kernel. Usa IPC y métricas de stall
(perf stat) para separar «ejecutando» de «esperando de forma costosa».
3) ¿Cuál es la métrica más simple para decir «gana el monstruo de caché»?
IPC más fallos de LLC bajo carga de producción. Si IPC es bajo y los fallos de LLC son altos, no estás hambriento de CPU; estás hambriento
de localidad y aciertos de caché.
4) ¿Más RAM siempre soluciona problemas de caché?
Más RAM ayuda si el conjunto de trabajo puede caber y puedes mantenerlo caliente (page cache, buffer pool, ARC). Pero la RAM no arregla
patrones de acceso patológicos, falsa compartición o mala colocación NUMA.
5) ¿Debería fijar procesos a núcleos?
A veces. Fijar puede mejorar la calidez de la caché y reducir la migración. Pero también puede empeorar el desbalance de carga.
Pruébalo con carga real. Si fijar mejora p99 y reduce fallos de LLC, mantenlo.
6) ¿Por qué mi upgrade a NVMe no mejoró mucho la latencia?
Porque ya estabas golpeando page cache o buffer pool, o porque tu latencia estaba dominada por stalls de CPU, locks o saltos de red.
Además, NVMe puede ser rápido y aun así tener una latencia tail desagradable bajo saturación.
7) ¿No es Direct I/O más rápido porque evita doble caché?
Puede serlo para cargas específicas (lecturas/escrituras grandes y secuenciales, streaming). Para patrones mixtos o con re-lecturas, el page cache
es una característica de rendimiento. El retirarlo a menudo convierte «velocidad de memoria» en «velocidad de almacenamiento».
8) ¿Cómo sé si NUMA me está perjudicando?
Si tienes sistemas multi-socket, asume que NUMA importa. Confírmalo con numastat y perf. Síntomas incluyen escalado pobre, latencia aumentada y diferencias de rendimiento
dependiendo de dónde el planificador ponga los hilos.
9) ¿Puede una CPU con menos núcleos superar a una con más núcleos?
Sí, si la CPU de menos núcleos tiene más caché por núcleo, mejor latencia de memoria o evita la saturación de ancho de banda. Muchos servicios no
escalan linealmente con núcleos porque el acceso a datos y la contención dominan.
10) ¿Cuál es el «error de caché» más común en código de aplicación?
Estructuras de datos que destruyen la localidad: grafos con muchos punteros, iteración aleatoria de hashmaps y asignar millones de objetos pequeños.
También: falsa compartición en contadores y colas multihilo.
Próximos pasos prácticos
Si sacas una lección operativa de esto: deja de tratar la velocidad de la CPU como protagonista. En producción, es un actor secundario. Los papeles principales
son localidad, caché y latencia.
- Ejecuta
perf statbajo carga real y registra IPC, fallos de LLC y ciclos detenidos. - Ejecuta
iostat -xy observa latencia y profundidad de cola, no solo MB/s. - Revisa fallos mayores y comportamiento de reclaim para ver si estás cayendo de memoria a disco.
- Valida la colocación NUMA y prueba fijado como experimento, no como dogma.
- Sólo después de esas mediciones, decide: más caché (otra SKU de CPU), más RAM, mejor layout de datos o una arquitectura diferente (scale out).
Luego haz lo aburrido: documenta la línea base, codifica las comprobaciones en tu runbook de despliegue y convierte «caché y localidad»
en un requisito de rendimiento de primera clase. Tu yo futuro, atrapado en un bridge de incidentes a las 2 a.m., te lo agradecerá extrañamente.