Sistemas grandes, errores pequeños: por qué «una línea» puede costar una fortuna

¿Te fue útil?

Los incidentes más caros en los que he trabajado nunca fueron causados por la “complejidad”. Fueron causados por la confianza.
Alguien cambió una línea—a veces una bandera, a veces un valor por defecto, a veces una solución “temporal”—y el sistema hizo exactamente lo que se le dijo.
Ahí está el problema.

En entornos de producción grandes, no obtienes fallos parciales como una cortesía. Obtienes comportamientos en cascada, amplificados por la automatización, cachés, reintentos y autocorrecciones bienintencionadas.
Un error de una línea se convierte en un evento a nivel de flota, y la factura llega en forma de ingresos perdidos, usuarios enfadados y una semana de arqueología forense.

Por qué «una línea» es peligrosa en sistemas grandes

El mito: una línea no puede ser tan grave. La realidad: una línea suele ser lo único que separa tu sistema de sus peores instintos.
Los sistemas grandes están cargados de factores multiplicativos: escala, automatización, reintentos, coordinación distribuida, infraestructura compartida y valores por defecto “útiles”.
Una pequeña mala configuración no solo rompe una máquina; cambia el comportamiento en todos los lugares donde se aplica esa línea.

Un cambio de una línea rara vez tiene un efecto de una sola línea. Cambia los tiempos. El tiempo cambia las colas. Las colas cambian la latencia.
La latencia provoca timeouts. Los timeouts provocan reintentos. Los reintentos aumentan la carga. La carga aumenta la latencia. Felicidades: has construido un bucle de realimentación.

La mayoría de los postmortems admiten eventualmente la misma verdad incómoda: el sistema estaba técnicamente “saludable” hasta que los humanos le enseñaron una lección nueva y peor.
Y como el cambio fue pequeño, evitó el filtro de escepticismo de todos. Los cambios pequeños se sienten seguros. No lo son.

Aquí está la regla operativa que te ahorrará dinero: trata los cambios pequeños como de alto riesgo cuando el blast radius es grande.
Un cambio de una línea en un almacén de configuración global no es “pequeño”. Es una transmisión.

La única línea que importa es la que ejecuta el sistema

La gente discute sobre la intención. Las computadoras discuten tablas de verdad.
Puedes “querer” fijar un timeout a 30 segundos y accidentalmente fijarlo a 30 milisegundos. El sistema cumplirá al instante, como un becario leal sin contexto.

Broma #1 (corta, relevante): La producción es donde tus suposiciones van a ser auditadas por la realidad—a escala.

Por qué los ingenieros de almacenamiento se inquietan con un “simple ajuste de configuración”

El almacenamiento está en la ruta crítica de casi todo: bases de datos, logging, colas, contenedores y la maquinaria invisible del “estado”.
El almacenamiento también tiene peculiaridades: amplificación de escrituras, comportamiento de caché, semántica de fsync y planificación I/O del kernel. Una línea puede cambiar un modelo de comportamiento entero:
escrituras síncronas vs asíncronas, I/O directo vs buffered, compresión activada/desactivada, recordsize, opciones de montaje, profundidad de cola, o un solo sysctl que cambia los umbrales de páginas sucias.

Peor aún: las fallas de almacenamiento suelen ser desastres en cámara lenta. El sistema no se cae; se arrastra.
Así es como se desperdicia dinero: mantienes la flota funcionando mientras consume CPU en reintentos y espera I/O en silencio.

Hechos y contexto histórico: cambios pequeños, grandes consecuencias

La ingeniería de confiabilidad lleva mucho tiempo aprendiendo la misma lección con distintos sombreros.
Aquí hay algunos hechos y puntos de contexto concretos que importan cuando decides si un cambio “menor” merece un proceso de alto peso.

  1. Los accidentes del Therac-25 (década de 1980) son un ejemplo clásico de cómo las suposiciones de software y seguridad colapsaron. Fallos pequeños en la lógica combinados con el flujo de trabajo condujeron a sobredosis letales.
    No es “una línea”, pero sí es “lógica pequeña, impacto masivo”.
  2. El Mars Climate Orbiter (1999) se perdió por una discrepancia de unidades (imperial vs. metric). Es el modo de fallo canónico de “suposición equivocada”: los valores parecían plausibles hasta que la física no estuvo de acuerdo.
  3. Las interrupciones de DNS han derribado servicios importantes repetidamente porque DNS es una dependencia pequeña con un gran blast radius. Un ajuste de TTL o del resolver puede convertirse en un incidente global rápido.
  4. El apagón del noreste de 2003 involucró fallos de software y alarmas que ocultaron el problema hasta que este se propagó. Las fallas de observabilidad son primas de “una línea”: no puedes arreglar lo que no puedes ver.
  5. El comportamiento de writeback de Linux ha evolucionado durante años porque los valores por defecto pueden causar picos de latencia o colapsos de rendimiento bajo presión. Un sysctl puede trasladar el dolor del disco a los usuarios.
  6. El RAID write hole y las políticas de caché son lecciones de hace décadas: una sola configuración en un controlador (write-back vs write-through) cambia si una pérdida de energía es un evento de rendimiento o de pérdida de datos.
  7. Las “tormentas de reintentos” son una patología conocida en sistemas distribuidos. Convierten la lentitud transitoria en sobrecarga sostenida. Valores diminutos de timeout/retry pueden convertir a tus clientes en armas contra tus propios servidores.
  8. Configuration-as-code se hizo popular en gran parte porque la deriva manual de configuración causaba comportamientos impredecibles; la solución fue la repetibilidad. Irónicamente, también hizo más fácil desplegar una línea mala en todas partes a la vez.

Una cita que la gente de operaciones tiende a interiorizar tras suficientes noches en vela:
“La esperanza no es una estrategia.” — James Cameron

No es una cita romántica. Es una cita de runbook. Si tu historia de seguridad es “probablemente estará bien”, ya estás negociando con el incidente.

La física de la amplificación: cómo errores pequeños provocan incidentes

1) Los sistemas distribuidos no fallan con cortesía

Las fallas de nodo único son nítidas. Las fallas distribuidas se difuminan.
Un error sutil de configuración puede producir timeouts parciales, carga desigual y contención extraña.
Tu balanceador de carga sigue enviando tráfico. Tu autoscaler ve latencia y añade nodos. Tu base de datos ve más conexiones y comienza a chocar.
El gráfico parece una avalancha en cámara lenta.

2) Las colas ocultan problemas hasta que ya no

Las colas son maravillosas. Desacoplan productores y consumidores.
También actúan como tarjetas de crédito: retrasan el dolor y añaden intereses.
Un cambio de una línea que ralentiza a los consumidores en un 20% puede no alertar por horas, hasta que la acumulación alcanza un umbral y todo empieza a caducar por timeout.

3) Las cachés convierten la corrección en probabilidad

Una caché oculta la latencia, amplifica la carga y te hace olvidar cómo es un arranque en frío.
Una línea puede hundir tu hit rate: cambia el formato de la clave de caché, voltea la política de expulsión, ajusta el TTL o introduce cardinalidad.
De repente tu origen (a menudo una base de datos) está haciendo trabajo que no veía desde hace meses. Nunca fue aprovisionado para eso. Ahora es el cuello de botella y el chivo expiatorio.

4) El almacenamiento es donde “menor” se vuelve medible

En almacenamiento, los pequeños mandos importan porque cambian los patrones de I/O:
aleatorio vs secuencial, sync vs async, I/O pequeño vs grande, metadata-heavy vs streaming, buffered vs direct, compressible vs incompressible.
Un mismatch de recordsize en ZFS o una opción de montaje puede convertir una base de datos sana en un generador de latencia.

Broma #2 (corta, relevante): La latencia de almacenamiento es como un mal chiste—el tiempo lo es todo, y siempre lo notas en la pausa.

5) “Por defecto” no es sinónimo de “seguro”

Los valores por defecto son compromisos entre cargas de trabajo, hardware y apetitos de riesgo. Tu producción no es “promedio”.
Un valor por defecto que está bien para un servidor web puede ser terrible para una base de datos con muchas escrituras.
Peor aún, los defaults cambian entre versiones. Una actualización de paquete de una línea puede cambiar implícitamente cinco comportamientos que nunca documentaste.

Tres mini-historias corporativas (anonimizadas, dolorosamente plausibles)

Mini-historia #1: El incidente causado por una suposición equivocada

Una empresa SaaS mediana movió parte de su flota de un tipo de instancia a otro. Misma familia de CPU, NVMe “similar”, kernel más nuevo.
Un ingeniero senior hizo un pequeño cambio en un manifiesto de despliegue: aumentar el timeout de conexión para un cliente gRPC interno porque “la red es más rápida ahora”.
La suposición: red más rápida significa menor latencia; por lo tanto un timeout menor es “seguro”.

Lo que pasaron por alto: el servicio detrás del endpoint gRPC dependía de un clúster Postgres que ocasionalmente tenía stalls de fsync bajo cargas explosivas.
Esos stalls solían ser absorbidos por el viejo timeout más generoso. Con el nuevo timeout bajo, el cliente empezó a caducar rápidamente y a reintentar agresivamente.
Los reintentos aumentaron la carga en el servicio, lo que aumentó la contención en la base de datos, lo que aumentó los stalls de fsync. Retroalimentación positiva clásica.

El on-call vio “errores de red” y persiguió pérdida de paquetes. La red estaba bien.
Mientras tanto, los gráficos de la base de datos mostraban un número creciente de conexiones, aumento en lock wait y latencia I/O con picos.
El incidente no fue una falla única; fue un nuevo modo de oscilación introducido por un cambio de timeout bien intencionado.

La solución fue brutal en su simplicidad: revertir el timeout, limitar reintentos con jitter y añadir un circuit breaker.
El aprendizaje fue más importante: la latencia no es un escalar. Es una distribución. Cuando ajustas timeouts, eliges qué cola puedes tolerar.

Mini-historia #2: La optimización que salió mal

Otra organización operaba un gran clúster Elasticsearch para logs y analítica de seguridad. El ingesto era intenso, el almacenamiento caro, y alguien decidió “optimizar disco”.
Cambiaron una configuración relacionada con compresión y ajustaron opciones de sistema de archivos en los nodos de datos—una línea en un repo de gestión de configuración, desplegada gradualmente.
El cambio parecía razonable en una prueba de laboratorio con datos sintéticos.

En producción, el dataset tenía una forma muy diferente. Algunos índices se comprimían bien; otros no.
El uso de CPU subió. No poco—lo suficiente para estirar ciclos de GC y aumentar la latencia de indexación.
La latencia de indexación llevó a reintentos de bulk desde los shippers. Los reintentos aumentaron la carga de ingesta. La ingesta aumentó las fusiones de segmentos.
Las fusiones de segmentos aumentaron la amplificación de escritura en disco. Ahora los discos estaban más ocupados que antes de la “optimización de disco”.

Los gráficos contaban una historia confusa: la utilización de disco subió, la CPU subió y la latencia subió. ¿Cuál causó cuál?
La respuesta fue: sí.
Un cambio destinado a reducir el coste de disco aumentó el coste de CPU, lo que incrementó la presión de merges, lo que aumentó el coste de disco. El sistema encontró un nuevo equilibrio: peor.

Hicieron rollback. Luego ejecutaron un canary apropiado con datos tipo producción, centrado en latencias de cola y tasas de merge en lugar del throughput promedio.
La lección no fue “no optimices”. Fue “no optimices a ciegas”. Tu carga de trabajo es la prueba.

Mini-historia #3: La práctica aburrida pero correcta que salvó el día

Una plataforma de servicios financieros tenía reputación de ser dolorosamente conservadora. Los ingenieros se quejaban de ventanas de cambio, canarios y “demasiadas listas de verificación”.
Entonces un proveedor de almacenamiento envió una actualización de firmware para arreglar un problema conocido. La actualización requería un bump del driver del host y un pequeño cambio en una regla udev.
Una línea. ¿Inofensiva, verdad?

Su proceso forzó que el despliegue empezara en un único nodo no crítico con carga sintética más tráfico shadow real.
En minutos, la latencia en ese nodo mostró picos periódicos. No lo suficiente para fallar, pero sí visible en p99.
Detuvieron el despliegue antes de que tocara la flota principal.

La causa raíz resultó ser una interacción de profundidad de cola entre el nuevo driver y su planificador I/O del kernel.
El driver antiguo limitaba silenciosamente el I/O pendiente; el nuevo permitía colas más profundas, lo que mejoró el rendimiento pero dañó la latencia de cola bajo mezcla de lectura/escritura.
Su carga de trabajo priorizaba p99 sobre el throughput.

Ajustaron la profundidad de cola explícitamente y fijaron el scheduler por dispositivo. Luego repitieron el canary y desplegaron con seguridad.
Nadie escribió un postmortem heroico. Simplemente evitaron un incidente. Así es “aburrido y correcto”.

Guía de diagnóstico rápido: qué revisar primero/segundo/tercero

Cuando las cosas van mal, no hay tiempo para filosofía. Necesitas una secuencia que encuentre el cuello de botella antes de que la sala se llene de opiniones.
Este es el playbook que uso para incidentes de “el sistema está lento / hace timeouts / falla intermitentemente”, especialmente cuando el almacenamiento puede estar involucrado.

Primero: confirma el síntoma y su forma (latencia vs errores vs saturación)

  • ¿Es global o está limitado a una AZ/rack/pool de nodos?
  • ¿p50 bien pero p99 mal (latencia de cola), o también están mal las medias?
  • ¿Los errores se correlacionan con timeouts (lado cliente) o con fallos del servidor?

Segundo: encuentra la clase de cuello de botella (CPU, memoria, I/O, red, contención de locks)

  • CPU: alto user/system, cola de ejecución, throttling, steal time.
  • Memoria: reclaim, swap, OOM kills, churn del page cache.
  • I/O: iowait, latencia de disco, profundidad de cola, stalls de sistema de archivos, picos de fsync.
  • Red: pérdida de paquetes, retransmisiones, límites de conntrack, timeouts de DNS.
  • Locks: esperas de locks en la base de datos, contención de mutex, pausas de GC.

Tercero: determina si estás viendo causa o consecuencia

  • Alta CPU puede venir de compresión, cifrado, reintentos o bucles de logging.
  • Alto I/O puede venir de compactación, merges, checkpointing o una tormenta de misses en caché.
  • Errores de red pueden ser reales, o pueden ser timeouts provocados por servidores lentos.

Cuarto: busca el disparador de “una línea”

  • Deploys recientes, cambios de configuración, feature flags, actualizaciones de kernel/firmware.
  • Los cambios del lado cliente (timeouts, reintentos, concurrencia) son culpables frecuentes.
  • Los toggles relacionados con almacenamiento (sync, cache, opciones de montaje, scheduler) son silenciosos pero agudos.

Quinto: reduce el blast radius antes de perfeccionar la comprensión

  • Pausa rollouts, congela cambios de autoscaling, deshabilita reintentos agresivos.
  • Falla abierto/cerrado intencionalmente según el impacto al negocio.
  • Prefiere revertir el cambio sobre depurar en caliente, a menos que el revert sea riesgoso.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estas son tareas reales que puedes ejecutar durante un incidente. Cada una incluye un comando, salida de ejemplo, qué significa y qué decides a continuación.
No las memorices. Consérvalas en un runbook y úsalas de forma consistente.

Task 1: Check basic load, uptime, and whether the node is thrashing

cr0x@server:~$ uptime
 14:22:10 up 31 days,  2:03,  3 users,  load average: 22.14, 18.09, 12.77

Qué significa: Load average muy por encima del número de CPUs suele significar tareas ejecutables (contención de CPU) o sleep ininterrumpible (I/O wait).

Decisión: Revisa inmediatamente saturación de CPU y iowait (Tareas 2 y 3). Si la carga sube rápido, comienza a reducir tráfico o drenar nodos.

Task 2: See if CPU is the bottleneck or if “idle” is hiding I/O wait

cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.2.0 (server) 	01/22/2026 	_x86_64_	(16 CPU)

12:22:12 PM  CPU   %usr %nice %sys %iowait %irq %soft %steal %idle
12:22:13 PM  all   12.4  0.0   6.1   41.8    0.0   0.7    0.0   39.0

Qué significa: Alto %iowait sugiere que la CPU está esperando al disco, no “ocupada calculando”.

Decisión: Trata esto como un incidente de I/O hasta demostrar lo contrario. Pasa a iostat y cheques por proceso de I/O.

Task 3: Check disk latency and queueing quickly

cr0x@server:~$ iostat -x 1 3
Linux 6.2.0 (server) 	01/22/2026 	_x86_64_	(16 CPU)

Device            r/s     w/s   rkB/s   wkB/s  await  aqu-sz  %util
nvme0n1         220.0  1100.0  8800.0 64000.0  38.2    9.40   99.7

Qué significa: await en ~38ms con %util cerca de 100% indica que el dispositivo está saturado y las solicitudes están encoladas.

Decisión: Identifica quién está emitiendo I/O (Tarea 4/5). Considera throttling, mover carga o revertir un cambio que aumentó la amplificación de escritura.

Task 4: Find top I/O processes (fast triage)

cr0x@server:~$ iotop -o -b -n 3
Total DISK READ: 2.10 M/s | Total DISK WRITE: 85.30 M/s
  PID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN  IO%  COMMAND
18722 be/4  elastic    0.00 B/s  62.40 M/s    0.00 45.6 elasticsearch
 9421 be/4  postgres   0.00 B/s  18.70 M/s    0.00 21.0 postgres: checkpointer

Qué significa: Dos procesos están manejando escrituras; uno es un checkpointer de base de datos (por ráfagas) y otro es Elasticsearch (escrituras pesadas y constantes).

Decisión: Si esto coincide con un deploy/config reciente, reviértelo. Si no, busca cambios en el comportamiento aguas arriba (misses de caché, reintentos, picos de ingest).

Task 5: Confirm if you’re getting blocked in D state (I/O) and which syscall is stuck

cr0x@server:~$ ps -eo pid,stat,wchan:32,cmd | awk '$2 ~ /D/ {print}' | head
18722 D    io_schedule                    /usr/share/elasticsearch/jdk/bin/java ...
 9421 D    jbd2_log_wait_commit           postgres: checkpointer

Qué significa: Procesos en estado D están bloqueados esperando I/O. El canal de espera sugiere rutas del kernel (journaling, scheduler).

Decisión: Investiga presión de sistema de archivos/journal, salud del dispositivo y si ocurrió un cambio de opción de montaje o scheduler.

Task 6: Check filesystem space and inode exhaustion (boring, frequent)

cr0x@server:~$ df -hT
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4  900G  865G   35G  97% /

Qué significa: 97% lleno es zona de peligro: fragmentación, asignaciones más lentas y riesgo de alcanzar 100% con escrituras en ráfaga.

Decisión: Detén la hemorragia: rota logs, elimina temporales o añade capacidad. También revisa inodos:

cr0x@server:~$ df -ih
Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2   56M   56M     0  100% /

Qué significa: Agotamiento de inodos causa “No space left on device” incluso cuando quedan bytes.

Decisión: Identifica explosiones de archivos pequeños (directorios temporales, caches) y corrige la carga. El agotamiento de inodos suele ser síntoma de un proceso fuera de control o retención mal configurada.

Task 7: Detect kernel memory pressure and reclaim storms

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
12  6      0  10240  12000  80000    0    0   120  9800 8200 9900 12  6 38 44  0

Qué significa: Alto b (bloqueados), alto wa (I/O wait) y mucha salida bo indica presión de writeback.

Decisión: Si un sysctl de una línea cambió límites dirty o timing de writeback, reviértelo. Si no, identifica al escritor y reduce su ritmo.

Task 8: Check recent reboots, OOM kills, and storage errors in the journal

cr0x@server:~$ journalctl -p warning -S -2h | tail -n 20
Jan 22 13:41:08 server kernel: nvme nvme0: I/O 123 QID 4 timeout, aborting
Jan 22 13:41:08 server kernel: EXT4-fs warning (device nvme0n1p2): ext4_end_bio:343: I/O error 10 writing to inode 262911

Qué significa: Timeouts y errores I/O del sistema de archivos no son “problemas de rendimiento”. Son incidentes de confiabilidad.

Decisión: Inicia triage de hardware: revisa SMART/NVMe logs (Tarea 9), considera sacar el nodo del servicio y evita reinicios forzados repetidos que pueden empeorar la corrupción.

Task 9: Inspect NVMe health and error counters

cr0x@server:~$ sudo nvme smart-log /dev/nvme0
SMART/Health Information (NVMe Log 0x02)
critical_warning                    : 0x00
temperature                         : 57 C
available_spare                     : 100%
percentage_used                     : 12%
media_errors                        : 3
num_err_log_entries                 : 27

Qué significa: media_errors distinto de cero y aumento de entradas de error sugieren problemas reales del dispositivo, no solo ajuste de parámetros.

Decisión: Drena y reemplaza el dispositivo/nodo. Si esto empezó después de un cambio de firmware, revierte el firmware en nodos restantes y abre un caso con el proveedor.

Task 10: Verify mount options that change durability or latency behavior

cr0x@server:~$ findmnt -no TARGET,SOURCE,FSTYPE,OPTIONS /var/lib/postgresql
/var/lib/postgresql /dev/nvme0n1p2 ext4 rw,relatime,data=writeback,barrier=0

Qué significa: barrier=0 desactiva write barriers; data=writeback cambia las semánticas de journaling. Pueden mejorar velocidad y arruinar tu semana.

Decisión: Si esto es involuntario, revierte a opciones más seguras (para ext4 las barreras suelen estar activadas por defecto; el modo de journaling depende del perfil de riesgo). Confirma con tus requisitos de durabilidad de almacenamiento.

Task 11: Check TCP retransmits and packet loss (don’t blame storage prematurely)

cr0x@server:~$ ss -s
Total: 19432 (kernel 20110)
TCP:   14210 (estab 1200, closed 12650, orphaned 8, timewait 3200)

Transport Total     IP        IPv6
RAW	  0         0         0
UDP	  90        70        20
TCP	  1560      1400      160
INET	  1650      1470      180
FRAG	  0         0         0
cr0x@server:~$ netstat -s | egrep -i 'retrans|listen|drops' | head
    12847 segments retransmitted
    320 listen queue overflows

Qué significa: Retransmisiones y overflows de la cola de escucha pueden imitar lentitud de almacenamiento creando tormentas de timeouts.

Decisión: Si las retransmisiones aumentaron tras un cambio (TLS, MTU, load balancer), atiende la red primero. Si hay overflows, ajusta backlog y reduce contención en el loop de accept.

Task 12: Detect DNS or resolver issues that look like “the app is slow”

cr0x@server:~$ resolvectl status | sed -n '1,80p'
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 10.0.0.53
       DNS Servers: 10.0.0.53 10.0.0.54
cr0x@server:~$ dig +stats example.internal A | tail -n 5
;; Query time: 1200 msec
;; SERVER: 10.0.0.53#53(10.0.0.53) (UDP)
;; WHEN: Thu Jan 22 14:24:10 UTC 2026
;; MSG SIZE  rcvd: 86

Qué significa: Una consulta DNS de 1.2s hará que todo parezca roto, especialmente servicios que resuelven con frecuencia.

Decisión: Detén la hemorragia inmediata: cachea resultados DNS en clientes donde corresponda, reduce la frecuencia de resolución o falla a otros resolvers. Luego arregla el problema de DNS.

Task 13: Check systemd for restart storms (a one-line failure multiplier)

cr0x@server:~$ systemctl status api.service
● api.service - Example API
     Loaded: loaded (/etc/systemd/system/api.service; enabled)
     Active: activating (auto-restart) since Thu 2026-01-22 14:21:02 UTC; 3s ago
    Process: 23110 ExecStart=/usr/local/bin/api --config /etc/api/config.yaml (code=exited, status=1/FAILURE)
cr0x@server:~$ journalctl -u api.service -S -10m | tail -n 10
Jan 22 14:21:01 server api[23110]: FATAL: config: unknown field "reties"
Jan 22 14:21:02 server systemd[1]: api.service: Scheduled restart job, restart counter is at 58.

Qué significa: Un typo causa reinicios continuos, lo que añade carga (logs, churn de conexiones) y consume tiempo.

Decisión: Para la tormenta de reinicios (deshabilita o establece backoff), revierte la configuración y añade validación de esquema en CI para que los typos mueran antes de llegar a prod.

Task 14: For ZFS environments, check pool health and latency suspects

cr0x@server:~$ sudo zpool status
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 02:11:33 with 0 errors on Wed Jan 21 03:00:14 2026
config:

	NAME        STATE     READ WRITE CKSUM
	tank        ONLINE       0     0     0
	  mirror-0  ONLINE       0     0     0
	    sda     ONLINE       0     0     0
	    sdb     ONLINE       0     0     0

errors: No known data errors
cr0x@server:~$ sudo zfs get -o name,property,value -H compression,recordsize,atime,sync tank/db
tank/db	compression	zstd
tank/db	recordsize	128K
tank/db	atime	off
tank/db	sync	standard

Qué significa: La salud del pool está bien; ahora revisa las propiedades del dataset. Un mismatch de recordsize o la configuración de sync puede cambiar dramáticamente la latencia de una base de datos.

Decisión: Si la carga es una base de datos con escrituras aleatorias pequeñas, considera ajustar recordsize apropiadamente y verifica expectativas de sync. No cambies sync=disabled a menos que disfrutes explicando pérdida de datos.

Errores comunes: síntomas → causa raíz → solución

Esta es la parte donde dejamos de fingir que estos incidentes son raros.
La mayoría de los incidentes de “una línea” encajan en patrones repetibles. Aprende el olor y los detectarás antes.

1) Timeouts súbitos tras “reducir timeouts para UX más ágil”

Síntomas: Más 499/504, reintentos del cliente se disparan, los servidores parecen “bien” pero p99 explota.

Causa raíz: Timeout por debajo de la cola de latencia de una dependencia; el comportamiento de reintentos amplifica la carga.

Solución: Incrementa timeouts para cubrir p99.9 realista; limita reintentos con backoff exponencial + jitter; añade circuit breakers y bulkheads.

2) “El disco se volvió más lento” justo después de habilitar compresión o cifrado

Síntomas: CPU sube, pausas de GC aumentan, profundidad de cola I/O sube, el throughput puede bajar o volverse inestable.

Causa raíz: La CPU se convierte en el cuello de botella oculto; mantenimientos en background (merges/compaction) aumentan la amplificación de escritura.

Solución: Canary con datos tipo producción. Mide p95/p99 y actividad en background. Considera aceleración por hardware, algoritmos distintos o compresión selectiva.

3) Picos de latencia de fsync en la base de datos tras una “opción de montaje de rendimiento”

Síntomas: Latencia de commit en picos, checkpoints se estancan, timeouts de la aplicación se agrupan.

Causa raíz: Opciones de montaje cambiaron comportamiento de journaling/barriers, mismatch de scheduler I/O, o tuning de writeback dañó latencia de cola.

Solución: Revertir opciones de montaje inseguras; fijar scheduler; tunear profundidad de cola para latencia; asegurar que la durabilidad coincide con los requisitos.

4) Tormenta de reinicios a nivel de flota tras una edición trivial de configuración

Síntomas: Instancias hacen flap, logs explotan, servicios upstream ven churn de conexiones.

Causa raíz: Sintaxis/field mismatch en config; systemd u orquestador reinician agresivamente.

Solución: Añadir validación de config en CI, implementar backoff de reinicios, y requerir canary + smoke tests antes del rollout completo.

5) “Todo está lento” pero solo en arranque en frío o tras rotación de nodos

Síntomas: Alta latencia de lectura tras deploys, hit rate de caché cae, bases de datos se calientan.

Causa raíz: Evicción de caché o cambio de claves; caches locales de nodo no calentados; churn agresivo de pods resetea working sets.

Solución: Preserva caches cuando sea posible, calienta caches críticos, reduce churn y monitoriza hit rate como SLO de primera clase.

6) El almacenamiento parece bien, pero los clientes hacen timeout y encolan

Síntomas: Métricas del servidor parecen estables; los clientes ven fallos intermitentes; retransmisiones aumentan.

Causa raíz: Pérdida de red, mismatch de MTU, o límites de conntrack/listen sobrecargados; “lento” es en realidad “no se puede conectar con fiabilidad”.

Solución: Revisa retransmisiones, drops, conntrack; valida MTU end-to-end; tunear backlog; reducir churn de conexiones.

7) “No cambiamos nada” (sí lo hicieron)

Síntomas: El incidente coincide con no haber deploys de aplicación, pero el comportamiento cambió.

Causa raíz: Actualizaciones de kernel/firmware, cambios en la imagen base, upgrades de dependencias, deriva de gestión de configuración o un cambio de default.

Solución: Rastrear cambios en toda la pila. Trata los rollouts de “plataforma” con la misma rigurosidad que los cambios de aplicación.

Listas de verificación / plan paso a paso para cambios de una línea más seguros

Si quieres menos incidentes, deja de confiar en la memoria y las buenas intenciones.
Usa puertas explícitas. Al sistema no le importa que estabas ocupado.

Paso a paso: hacer un cambio de una línea sin provocar un incidente de una semana

  1. Clasifica el blast radius.
    Pregunta: ¿esta línea se aplica a un nodo, a un servicio, a una región o a toda la compañía?
    Si es global, trátala como un deploy de código.
  2. Nombra el modo de fallo que estás dispuesto a aceptar.
    Reducir un timeout significa aceptar más reintentos y fallos bajo lentitud.
    Cambiar ajustes de durabilidad significa aceptar posible pérdida de datos. Dilo en voz alta.
  3. Escribe el plan de rollback antes del cambio.
    Si el rollback requiere “después”, no tienes un plan de rollback.
  4. Construye un canary honesto.
    Un nodo con tráfico irreal no es un canary; es un laboratorio.
    Usa tráfico real (shadow si es necesario) y mide p95/p99.
  5. Define señales de “alto”.
    Ejemplos: p99 latency > X durante Y minutos, tasa de errores > Z, profundidad de cola > Q, disk await > A.
  6. Envía el cambio con observabilidad.
    Añade métricas/logs que confirmen el efecto esperado del cambio.
    Si no puedes medirlo, no lo toques en producción.
  7. Despliega gradualmente, con pausas.
    Usa rollout por etapas: 1 nodo → 1% → 10% → 50% → 100%.
    Pausa entre etapas el tiempo suficiente para observar el comportamiento de cola.
  8. Protégete contra tormentas de reintentos.
    Asegura presupuestos de reintento en clientes. Añade jitter. Limita concurrencia.
  9. Registra la decisión.
    Pon la razón junto a la línea en comentarios de código o en la descripción del cambio.
    El tú futuro estará cansado y suspicaz.
  10. Verificación post-cambio.
    Confirma no solo que “está arriba”, sino que la métrica objetivo se movió en la dirección deseada sin regresiones en las colas.

Qué evitar (opinión, porque me gusta dormir)

  • No cambies timeouts sin cambiar la política de reintentos.
  • No desactives “temporalmente” barreras de durabilidad para cumplir una fecha límite.
  • No despliegues actualizaciones de plataforma sin canarios conscientes de la aplicación.
  • No confíes en promedios. Observa p95/p99 y profundidad de cola.
  • No trates un repo de config como “seguro” solo porque no es código. Es intención ejecutable.

FAQ

1) ¿Por qué los cambios pequeños de configuración causan más incidentes que cambios grandes de código?

Porque las configs tienden a ser globales, rápidas de aplicar y poco probadas. Los cambios de código suelen pasar por CI, revisiones y despliegues escalonados.
Un cambio de configuración puede saltarse todo eso y aun así afectar a todo.

2) ¿Cuál es la forma más rápida de saber si la lentitud está relacionada con el almacenamiento?

Revisa mpstat para ver alto iowait, luego iostat -x para await, aqu-sz y %util.
Luego usa iotop para identificar el escritor/lector. Si el disco está saturado, el almacenamiento es al menos parte de la historia.

3) ¿Los reintentos son siempre malos?

Los reintentos son necesarios, pero los reintentos sin control son un ataque DDoS distribuido que ejecutas por accidente contra ti mismo.
Usa presupuestos de reintento, backoff exponencial, jitter y circuit breakers.

4) ¿Deberíamos revertir por defecto inmediatamente?

Si el incidente empezó justo después de un cambio y el rollback es seguro, sí.
Depurar en vivo es seductor y a menudo más lento que revertir. Excepciones: migraciones de esquema, transformaciones unidireccionales de datos o incidentes de seguridad.

5) ¿Cómo eliges timeouts sin adivinar?

Usa distribuciones de latencia en producción de las dependencias. Fija timeouts por encima del p99.9 realista más margen.
Luego asegúrate de que tu política de reintentos no multiplique la carga durante la degradación.

6) ¿Cuál es el error de almacenamiento más común por una “línea”?

Cambiar semánticas de durabilidad (write barriers, comportamiento de sync) por rendimiento.
Puede rendir bien en benchmarks y convertirse en corrupción o pérdida de datos tras un fallo o corte de energía.

7) ¿Por qué habilitar la compresión a veces empeora el uso de disco?

La compresión puede aumentar la CPU, lo que ralentiza indexing/compaction, aumentando la amplificación de escritura y el espacio temporal para segmentos/compactions.
Además, datos incompresibles siguen incurriendo en sobrecarga de metadata y procesamiento.

8) ¿Qué es “blast radius” en términos prácticos?

Es cuántos usuarios y sistemas se ven afectados cuando el cambio sale mal.
Un cambio en un nodo es un rasguño. Un cambio de configuración global es un incendio forestal. Planea en consecuencia.

9) ¿Cómo hacemos cambios de configuración más seguros sin frenar todo?

Trata la config como código: validación, canarios, rollout escalonado, triggers automáticos de rollback y registros de auditoría.
En general irás más rápido porque pasarás menos tiempo en incidentes.

10) ¿Qué pasa si no podemos canary porque el cambio solo funciona de forma global?

Entonces necesitas un canary sintético: duplicar tráfico, lecturas shadow o un entorno paralelo que refleje dependencias clave.
Si realmente no puedes validar con seguridad, el cambio es inherentemente riesgoso—planifícalo como tal.

Conclusión: pasos siguientes que realmente reducen incidentes

Los sistemas grandes no te castigan por escribir mal código. Te castigan por enviar suposiciones no probadas a una máquina que las escala perfectamente.
La “una línea” no es la villana; la villana es el blast radius sin límites combinado con bucles de retroalimentación débiles.

Pasos prácticos a seguir:

  • Pon una superficie de configuración de alto riesgo (timeouts, reintentos, opciones de montaje, caching) detrás de rollouts escalonados.
  • Añade validación automática de config en CI/CD para que typos y mismatches de esquema no lleguen a prod.
  • Instrumenta latencia de cola y profundidad de colas en todos los límites de dependencia.
  • Escribe (y ensaya) procedimientos de rollback que no requieran heroicidad.
  • Normaliza la “corrección aburrida”: canarios, pausas y señales de alto. Es más barato que la adrenalina.

Si no te llevas nada más: trata los cambios pequeños como eventos potencialmente grandes, porque en producción la escala es un multiplicador y el tiempo es un impuesto.
Paga por adelantado.

← Anterior
Debian 13: NTP funciona pero persiste la deriva — Ajustes del reloj de hardware y de Chrony (Caso #19)
Siguiente →
Incompatibilidad de versión PHP en WordPress: comprobar y actualizar sin interrupciones

Deja un comentario