Fundamentos 6 min de lectura

Los 10 Jinetes del Apocalipsis del Software

Publicado el 22 de septiembre de 2025

Los 10 Jinetes del Apocalipsis del Software

En el desarrollo de software, ciertos fallos se repiten una y otra vez: problemas de concurrencia, fugas de recursos, desbordamientos numéricos, efectos en cascada y dependencias que se vuelven ingobernables. En este artículo repaso diez de los peores: Race Conditions, Deadlocks, Memory Leaks, Integer Overflow, Thundering Herd, Cache Stampede, inyección SQL/NoSQL, Buffer Overflow, Dependency Hell y Retry Storms. No son teoría; son fallos que derrumban sistemas en producción.

1. Race Condition (condición de carrera)

Qué es: Dos o más hilos o procesos acceden a un recurso compartido y el resultado depende del orden en que se ejecutan. Si ese orden no está garantizado, el estado final puede ser incorrecto.

Ejemplo: Un contador compartido que dos hilos incrementan. Sin sincronización, puedes perder incrementos o leer valores inconsistentes.

Cómo mitigar: Cerraduras (locks), estructuras atómicas, inmutabilidad, o diseños que eviten estado compartido (por ejemplo, colas y workers con un solo consumidor).

2. Deadlock (bloqueo mutuo)

Qué es: Dos o más hilos se bloquean entre sí porque cada uno espera un recurso que otro tiene. Nadie avanza.

Ejemplo: Hilo A tiene el lock 1 y espera el lock 2; hilo B tiene el lock 2 y espera el lock 1.

Cómo mitigar: Orden fijo al adquirir locks, timeouts, evitar múltiples locks cuando sea posible, o patrones como “try-lock” y retroceso.

3. Memory Leak (fuga de memoria)

Qué es: Memoria que se asigna pero nunca se libera. Con el tiempo, el proceso consume toda la RAM disponible y termina cayendo o siendo matado por el SO.

Ejemplo: Listeners o suscripciones que no se eliminan, caches sin límite, referencias que mantienen objetos vivos sin necesidad.

Cómo mitigar: Revisar ciclos de vida (registrar y desregistrar), límites en caches, profiling y monitoreo de memoria en staging/producción.

4. Integer Overflow / Underflow

Qué es: Un cálculo entero excede el rango representable (overflow) o se va por debajo (underflow). En lenguajes sin chequeo automático, el valor “da la vuelta” y puede usarse en índices, tamaños o dinero con resultados catastróficos.

Ejemplo: total = cantidad * precio con enteros pequeños puede overflow; un índice negativo por underflow puede escribir fuera de array.

Cómo mitigar: Usar tipos con más rango cuando haga falta, validar rangos antes de operaciones críticas, y en lenguajes que lo permitan, tipos “checked” o bibliotecas de big integers para dinero y conteos grandes.

5. Thundering Herd (estampida)

Qué es: Muchos clientes o procesos reaccionan al mismo evento (por ejemplo, expiración de un lock o de una entrada de caché) y todos atacan el mismo recurso a la vez. Picos de carga masivos que pueden tumbar el sistema.

Ejemplo: Miles de workers que dormían esperando un lock; cuando se libera, todos se despiertan y golpean la base de datos al mismo tiempo.

Cómo mitigar: Colas, backoff exponencial, un solo “leader” que renueva o refresca, o aleatorización en el tiempo de reintento para dispersar la carga.

6. Cache Stampede (estampida de caché)

Qué es: Una clave de caché expira; muchas peticiones llegan a la vez, no encuentran valor en caché y todas disparan la misma operación costosa (por ejemplo, una query pesada). El backend se satura.

Ejemplo: Caché de “página principal” que expira a las 00:00; miles de usuarios cargan la página y todos generan el mismo contenido a la vez.

Cómo mitigar: “Single flight” (solo una petición regenera, el resto espera), “probabilistic early expiration”, o refresco en background antes de que expire (stale-while-revalidate).

7. Inyección SQL / NoSQL

Qué es: Entrada del usuario se concatena directamente en una consulta (SQL o NoSQL) sin escapar ni usar parámetros. Un atacante puede inyectar lógica y leer, modificar o borrar datos.

Ejemplo: "SELECT * FROM users WHERE id = " + userInput permite inyectar 1 OR 1=1 o sentencias completas.

Cómo mitigar: Siempre consultas parametrizadas o ORMs que las usen; validación y sanitización de entrada; principio de mínimo privilegio en la BD; nunca confiar en la entrada del usuario dentro de la consulta.

8. Buffer Overflow

Qué es: Se escribe más datos de los que caben en un buffer asignado. En C/C++ y en contextos de bajo nivel, eso puede sobrescribir memoria adyacente (variables, direcciones de retorno) y abrir la puerta a exploits.

Ejemplo: strcpy(dest, userInput) con userInput más largo que dest.

Cómo mitigar: Usar APIs seguras (strncpy, buffers con tamaño conocido), lenguajes o rutinas que comprueben límites; en código moderno, preferir lenguajes con seguridad de memoria (Rust, Go, etc.) donde sea posible.

9. Dependency Hell (infierno de dependencias)

Qué es: Proyecto con muchas dependencias; versiones incompatibles entre ellas (A pide lib X 1.0, B pide X 2.0), actualizaciones que rompen APIs, o árboles de dependencias enormes y frágiles. Builds o despliegues imposibles o no reproducibles.

Ejemplo: Actualizar una librería para un fix de seguridad y descubrir que otra depende de una versión antigua con API distinta.

Cómo mitigar: Lockfile (package-lock, yarn.lock, Cargo.lock, etc.), menos dependencias directas, revisar y actualizar de forma incremental, y usar herramientas que detecten conflictos y vulnerabilidades.

10. Retry Storms (tormentas de reintentos)

Qué es: Un servicio se degrada o cae; los clientes reintentan una y otra vez. El volumen de reintentos multiplica la carga sobre el servicio caído y sobre dependencias compartidas, impidiendo la recuperación.

Ejemplo: Un API que tarda mucho; cada cliente hace retry cada pocos segundos; en segundos tienes 10x más requests y el sistema colapsa del todo.

Cómo mitigar: Backoff exponencial y jitter en los reintentos, circuit breaker para dejar de llamar cuando el servicio está caído, límites de concurrencia y colas, y timeouts claros para no mantener conexiones colgadas.

Mi perspectiva personal

Estos “jinetes” no son excepciones; son riesgos estructurales. Race conditions y deadlocks aparecen cuando hay concurrencia y estado compartido. Memory leaks e integer overflow cuando no se piensa en límites y ciclos de vida. Thundering herd y cache stampede cuando muchos actores reaccionan al mismo evento. Inyección y buffer overflow cuando la entrada del usuario se confía sin defensas. Dependency hell y retry storms cuando no se gestionan dependencias y fallos de forma explícita.

La mitigación no es “evitar la concurrencia” o “no usar dependencias”; es diseñar con estos fallos en mente: menos estado compartido, más inmutabilidad, validación y parámetros preparados, límites y backoff, y observabilidad para detectarlos antes de que sea tarde. Conocer estos diez te ayuda a anticipar dónde puede romperse un sistema y a tomar decisiones de diseño más seguras.