Cómo la IA Reescribirá el Pipeline Gráfico: El Fotograma Medio Generado

Todo equipo de gráficos conoce esa sensación: la demo alcanza 60 fps en la máquina del ingeniero principal y luego se derrumba en un trabado en el hardware real—justo cuando comienza la captura de marketing. Ahora añade IA al pipeline: no sólo estás desplegando shaders y texturas; estás desplegando un modelo, un runtime, controladores y heurísticas “útiles” que pueden convertir un fotograma malo en todo un segundo de remordimiento visual.

La próxima década del renderizado en tiempo real no es “la IA reemplaza la rasterización.” Es más desordenada e interesante: cada fotograma se convierte en un acuerdo negociado entre el render clásico y la inferencia neuronal. Medio renderizado, medio generado. Y las operaciones serán responsables del resultado—latencia, memoria, determinismo, regresiones y artefactos raros que solo aparecen después de tres horas en un bioma desértico con niebla activada.

Qué significa realmente “medio generado”

Cuando la gente dice “gráficos con IA”, normalmente se refieren a una de tres cosas: (1) ampliar (upscale) un render de baja resolución a alta resolución, (2) generar fotogramas intermedios entre fotogramas reales, o (3) desruido de una imagen producida por un render ruidoso (path tracing, efectos estocásticos). Esos son los usos generalizados, comerciales y “que funcionan un martes”.

Pero “el fotograma que está medio generado” es más amplio. Es un pipeline donde el motor renderiza deliberadamente menos de lo que requiere la imagen final y usa IA para rellenar lo que se omitió—resolución, muestras, detalle de geometría, detalle de shading, incluso partes del G-buffer. En otras palabras, la IA no es un post-proceso. Es un coprocesador que compensa cómputo o tiempo faltante.

La distinción operativa importante: un post-proceso puede desactivarse cuando las cosas se ponen mal. Un coprocesador cambia lo que significa “salida correcta”. Eso afecta las pruebas, la depuración y lo que puedes revertir razonablemente bajo presión de incidentes.

El modelo mental que no te fallará

Trata el fotograma híbrido como una transacción multietapa con presupuestos estrictos:

  • Entradas: profundidad, vectores de movimiento, exposición, jitter, fotogramas previos, a veces normales/albedo.
  • Render clásico: raster, compute, quizá trazado de rayos parcial con muestras/res reducidos.
  • Inferencia neuronal: reconstruir o sintetizar detalles faltantes a partir de entradas más historial.
  • Composición: HUD/UI, elementos con alpha, post FX que deben permanecer nítidos y estables.
  • Presentación: pacing de fotogramas, VRR, generación de fotogramas, implicaciones para captura/streaming.

Si no puedes describir qué etapa posee qué píxeles, no puedes depurarlo. “El modelo lo hizo” no es una causa raíz. Es una confesión.

Dónde encaja la IA en el pipeline (y dónde no debería)

1) Upscaling: comprar píxeles con matemáticas

El upscaling es la droga de entrada porque es fácil de justificar: renderiza al 67% de resolución, gasta un par de milisegundos en reconstrucción y entrega una imagen más nítida que un bilinear ingenuo. El problema operativo es que los upscalers son temporales. Usan historial. Eso significa:

  • Los vectores de movimiento deben ser correctos o obtendrás ghosting y bordes “elásticos”.
  • La exposición/tonemapping debe ser estable o obtendrás shimmer y respiración.
  • Los cortes de cámara, las superposiciones de UI y las partículas se convierten en casos especiales.

2) Generación de fotogramas: comprar tiempo con predicción

La generación de fotogramas (FG) inserta fotogramas sintetizados por IA entre fotogramas “reales”. Esto no es lo mismo que duplicar el rendimiento. Estás intercambiando latencia y ocasionales alucinaciones por un movimiento más suave. Eso está bien para algunos juegos, horrible para otros y complicado para cualquier cosa competitiva.

La pregunta central de SRE: ¿cuál es tu SLO de latencia? Si no puedes responder, básicamente estás jugando a los dados con la entrada del usuario. A veces los dados salen “mantequilla”. A veces salen “¿por qué falló mi parada?”

3) Denoising: comprar muestras con priors

El denoising es donde los métodos neuronales se sienten inevitables. El path tracing te da iluminación físicamente plausible pero ruidosa con conteos de muestras bajos. Los denoisers neuronales convierten un puñado de muestras en algo presentable apoyándose en priors aprendidos. Genial—hasta que los priors están equivocados para tu contenido.

Los denoisers también crean una trampa sutil de fiabilidad: tu renderer podría ser “correcto”, pero tu denoiser es sensible al encoding de entrada, la precisión de normales o diferencias sutiles en rangos de roughness. Dos shaders que se ven idénticos en un pipeline clásico pueden divergir una vez desruidos.

4) Los lugares que la IA no debería controlar (a menos que te gusten los incendios)

  • UI y texto: mantenlos en resolución nativa, tardíos en el pipeline. No dejes que la reconstrucción temporal difumine tu tipografía.
  • Feedback competitivo de impacto: si una etapa de IA puede crear o eliminar una señal, recibirás informes de bugs redactados como amenazas legales.
  • Visualización crítica para seguridad: simuladores de entrenamiento, imagen médica, cualquier cosa donde una alucinación sea una responsabilidad.
  • Sistemas de reproducción deterministas: si tu juego depende de que las repeticiones coincidan exactamente, las etapas de IA deben hacerse deterministas o excluirse.

Presupuestos de latencia: la única verdad que importa

Los viejos argumentos de render se centraban en fps. Los nuevos argumentos tratan sobre pacing y latencia de extremo a extremo. La IA tiende a mejorar el throughput promedio mientras empeora la latencia en la cola (tail latency), porque la inferencia puede tener efectos de caché, peculiaridades de planificación del driver y rutas ocasionalmente lentas (compilación de shaders, warmup del modelo, paginación de memoria, transiciones de estado de energía).

Un pipeline de producción necesita presupuestos que se vean como SLOs:

  • Tiempo de fotograma p50: el caso normal.
  • Tiempo de fotograma p95: lo que los usuarios recuerdan como “tartamudeo”.
  • Tiempo de fotograma p99: lo que los streamers recortan y convierten en memes.
  • Latencia input-to-photon: lo que los jugadores competitivos sienten en sus manos.
  • Holgura de VRAM: lo que previene paginación intermitente y picos catastróficos.

La generación de fotogramas complica las cuentas porque tienes dos relojes: la cadencia de simulación/render y la cadencia de pantalla. Si tu simulación corre a 60 y muestras a 120 con fotogramas generados, tu movimiento parece más suave pero la latencia de entrada está ligada a la cadencia de la simulación más el buffering. Esto no es un juicio moral. Es física más colas.

Un pipeline híbrido fiable hace dos cosas agresivamente:

  1. Mide la latencia explícitamente (no sólo fps).
  2. Mantiene holgura en VRAM, tiempo GPU y envío en CPU para que los picos no se conviertan en outages.

Una cita que deberías mantener pegada a tu monitor, porque aplica aquí más que en ningún otro lugar: “La esperanza no es una estrategia.” — Gene Kranz.

Broma #1: Si tu plan es “probablemente el modelo no hará picos”, felicidades—has inventado presupuestación probabilística, también conocida como apostar.

Rutas de datos y telemetría: trata los fotogramas como transacciones

La depuración gráfica clásica ya es difícil: un millón de piezas móviles, cajas negras de drivers y bugs sensibles al tiempo. La IA añade una nueva categoría de “mal silencioso”: el fotograma se ve plausible, pero no es fiel. Peor: depende del contenido. El bug sólo aparece en cierto mapa, a cierta hora del día, con cierto efecto de partículas, después de que la GPU se ha calentado.

Los sistemas de producción sobreviven gracias a la observabilidad. El renderizado híbrido necesita la misma disciplina. Deberías registrar y visualizar:

  • Tiempos GPU por etapa: render base, inferencia, post, present.
  • Profundidad de colas y backpressure: ¿se acumulan fotogramas en algún lugar?
  • Asignaciones de VRAM a lo largo del tiempo: no sólo “usado”, sino “fragmentado” y “evicto”.
  • Métricas de inferencia: versión del modelo, modo de precisión, forma de batch, estado warm/cold.
  • Indicadores de calidad: % de validez de vectores de movimiento, tasa de desoclusión, cobertura de máscaras reactivas.

La victoria operativa más simple: estampa cada fotograma con un “manifiesto de pipeline” que registre los toggles y versiones clave que lo influenciaron. Si no puedes responder “¿qué versión del modelo produjo este artefacto?” no tienes un bug—tienes una novela de misterio.

Hechos y contexto histórico que explican las compensaciones actuales

  • 1) El antialiasing temporal (TAA) popularizó la idea de que “el fotograma actual no es suficiente”. Los upscalers modernos heredaron esa visión.
  • 2) Los primeros pipelines GPU eran de función fija; la programabilidad (shaders) convirtió los gráficos en software, y el software siempre atrae automatización.
  • 3) Los renderizadores offline usaron denoising mucho antes que en tiempo real; las pipelines de producción cinematográfica demostraron que puedes cambiar muestras por reconstrucción más inteligente.
  • 4) El renderizado en tablero (checkerboard) en consolas fue un precursor del upscaling ML: renderizar menos píxeles y reconstruir el resto usando patrones e historial.
  • 5) Los vectores de movimiento existían para motion blur y TAA antes de convertirse en entradas críticas para la IA; ahora un buffer de velocidad malo es una caída de calidad.
  • 6) El ray tracing en hardware hizo factible “ruidoso pero correcto”; los denoisers neuronales hicieron factible “enviable” dentro de presupuestos en tiempo real.
  • 7) La industria aprendió de incidentes de streaming de texturas: los picos de VRAM no fallan con gracia—fallan como una trampilla bajo tus pies.
  • 8) Las consolas forzaron pensar en rendimiento determinista; la IA reintroduce varianza a menos que la diseñes para evitarla.
  • 9) Los codificadores de video ya hacen predicción compensada por movimiento; la generación de fotogramas es conceptualmente adyacente, pero debe tolerar interactividad.

Nuevos modos de fallo en el renderizado híbrido

Taxonomía de artefactos que deberías usar realmente

  • Ghosting: historial sobreconfiado; vectores de movimiento equivocados o desoclusión no manejada.
  • Shimmering: inestabilidad temporal; exposición, jitter o bucle de retroalimentación de reconstrucción.
  • Smearing: inferencia suavizando detalle que debería ser de alta frecuencia (follaje, cables finos).
  • Bordes alucinados: el upscaler inventa estructura; usualmente por entradas insuficientemente especificadas.
  • Contaminación de UI: la etapa temporal ve la UI como contenido de escena y la arrastra en el tiempo.
  • La latencia “se siente rara”: generación de fotogramas y buffering; a veces compuesta por modos reflexivos mal configurados.
  • Picos aleatorios: paginación de VRAM, warmup de modelos, compilación de shaders, cambios de estado de energía, procesos en segundo plano.

La trampa de fiabilidad: la IA oculta deuda de render

El renderizado híbrido puede enmascarar problemas subyacentes: vectores de movimiento inestables, profundidad inconsistente, máscaras reactivas faltantes, manejo incorrecto de alpha. El modelo lo cubre… hasta que el contenido cambia y la tapadera falla. Entonces estás depurando dos sistemas a la vez.

Si envías renderizado híbrido, debes mantener una vía de fallback “sin IA” que esté probada en CI, no sólo teóricamente posible. Esta es la diferencia entre un modo degradado y un outage.

Broma #2: El renderizado neuronal es como un colega que termina tus frases—impresionante hasta que empieza a hacerlo en reuniones con tu jefe.

Guía rápida de diagnóstico

Cuando el rendimiento o la calidad se tuercen, no empieces discutiendo “IA vs raster.” Comienza encontrando el cuello de botella con un enfoque despiadado y por etapas. El objetivo es identificar qué presupuesto está roto: tiempo GPU, envío CPU, VRAM o latencia/pacing.

Primero: confirma que el síntoma es pacing, no fps promedio

  • Revisa p95/p99 picos de tiempo de fotograma y si se correlacionan con transiciones de escena, cortes de cámara o efectos.
  • Confirma si el tartamudeo se alinea con presión de VRAM o eventos de compilación de shaders.
  • Valida que la ruta de presentación (VRR, vsync, limitador) coincida con las suposiciones de la prueba.

Segundo: aisla “render base” vs “inferencia IA” vs “present”

  • Desactiva la generación de fotogramas primero (si está activada). Si la latencia y el pacing se normalizan, estás en el dominio de presentación/interpolación.
  • Vuelve a resolución nativa (desactiva upscaling). Si los artefactos desaparecen, tus entradas (vectores de movimiento, máscara reactiva) son sospechosas.
  • Cambia el denoiser a un modo más simple o baja calidad. Si los picos desaparecen, la inferencia es la culpable (o su comportamiento de memoria).

Tercero: revisa holgura de VRAM y paginación

  • Si la VRAM está dentro del 5–10% del límite, asume que paginarás bajo cargas reales.
  • Busca picos periódicos: suelen coincidir con streaming, churn de asignaciones tipo GC o captura en segundo plano.
  • Confirma que los pesos del modelo están residentes y no se vuelven a subir por pérdida de contexto o presión de memoria.

Cuarto: valida entradas e integridad del historial

  • Vectores de movimiento: espacio correcto, escala correcta, manejo correcto para meshes con skinning y partículas.
  • Profundidad: precisión estable y mapeo near/far consistente; evita desajustes de reversed-Z “útiles” entre pases.
  • Reset de historial en cortes: si no cortas el historial, el modelo intentará pegar dos fotogramas no relacionados.

Quinto: control de regresiones

  • Fija versiones de drivers para baseline de QA. No depures dos objetivos en movimiento al mismo tiempo.
  • Fija versiones de modelos y modos de precisión. Si no puedes reproducir, no puedes arreglar.
  • Usa feature flags con kill-switches que ops pueda activar sin recompilar.

Tareas prácticas: comandos, salidas y decisiones

Estos son checks de nivel ops que puedes ejecutar en una estación Linux o servidor de compilación. No van a depurar mágicamente tu código de shader, pero te dirán si estás peleando con la GPU, la pila del driver, la presión de memoria o tu propio proceso.

Task 1: Identificar la GPU y el driver en el entorno exacto

cr0x@server:~$ lspci -nn | grep -Ei 'vga|3d|display'
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation AD104 [GeForce RTX 4070] [10de:2786] (rev a1)

Qué significa: Has confirmado la clase de hardware. Esto importa porque el comportamiento de inferencia difiere por arquitectura.

Decisión: Si los informes de bug mencionan distintos device IDs, divide el problema por arquitectura primero; no los promedies.

Task 2: Confirmar versiones del kernel driver y firmware

cr0x@server:~$ uname -r
6.5.0-21-generic

Qué significa: Las actualizaciones del kernel pueden cambiar el comportamiento DMA, la planificación y los defaults de IOMMU—suficiente para alterar el tartamudeo.

Decisión: Fija el kernel para baselines de pruebas de rendimiento. Actualiza intencionalmente, no por accidente.

Task 3: Confirmar versión del driver NVIDIA (o pila equivalente)

cr0x@server:~$ nvidia-smi
Wed Jan 21 10:14:32 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.14              Driver Version: 550.54.14      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------|
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4070        Off |   00000000:01:00.0  On |                  N/A |
| 30%   54C    P2              95W / 200W |     7420MiB / 12282MiB |     78%      Default |
+-----------------------------------------+------------------------+----------------------+

Qué significa: Versión de driver y uso de VRAM son visibles. 7.4 GiB usados no es alarmante; 11.8/12.2 sí lo es.

Decisión: Si la VRAM está consistentemente >90%, trátalo como riesgo de paginación y reduce presupuestos (texturas, buffers RT, tamaño de modelo, buffers de historial).

Task 4: Vigilar VRAM y utilización a lo largo del tiempo para captar picos

cr0x@server:~$ nvidia-smi dmon -s pucm -d 1 -c 5
# gpu   pwr gtemp mtemp    sm   mem   enc   dec  mclk  pclk
# Idx     W     C     C     %     %     %     %   MHz   MHz
    0    94    55     -    81    63     0     0  9501  2580
    0   102    56     -    88    66     0     0  9501  2610
    0    73    53     -    52    62     0     0  9501  2145
    0   110    57     -    92    70     0     0  9501  2655
    0    68    52     -    45    61     0     0  9501  2100

Qué significa: Puedes ver ráfagas. Si mem% sube y luego baja, puede que estés paginando o reasignando agresivamente.

Decisión: Correlaciona picos con eventos del motor (zonas de streaming, cinemáticas). Añade pre-warm o limita las asignaciones.

Task 5: Confirmar ancho de enlace/speed PCIe (ocurren estrangulamientos ocultos)

cr0x@server:~$ sudo lspci -s 01:00.0 -vv | grep -E 'LnkCap|LnkSta'
LnkCap: Port #0, Speed 16GT/s, Width x16
LnkSta: Speed 16GT/s, Width x16

Qué significa: No estás atrapado en x4 porque alguien usó la ranura equivocada o una configuración BIOS.

Decisión: Si el enlace está degradado, arregla hardware/BIOS antes de “optimizar” tu renderer hasta deformarlo.

Task 6: Comprobar escalado de frecuencia de CPU (asesino del pacing de fotogramas)

cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
powersave

Qué significa: La CPU puede ser lenta para aumentar, provocando tartamudeo en el hilo de render.

Decisión: Para pruebas de rendimiento, configura a performance y documenta, o tus resultados son ficción.

Task 7: Establecer governor de rendimiento durante benchmarks controlados

cr0x@server:~$ sudo cpupower frequency-set -g performance
Setting cpu: 0
Setting cpu: 1
Setting cpu: 2
Setting cpu: 3

Qué significa: La CPU mantendrá relojes más altos de forma consistente.

Decisión: Si los tartamudeos desaparecen, tienes un problema de planificación/potencia de CPU, no un “la IA es lenta”.

Task 8: Comprobar presión de memoria y actividad de swap

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            62Gi        41Gi       3.1Gi       1.2Gi        18Gi        19Gi
Swap:          8.0Gi       2.4Gi       5.6Gi

Qué significa: El uso de swap sugiere que el sistema está paginando. Eso puede manifestarse como picos periódicos y hitching de assets.

Decisión: Reduce la huella de memoria, corrige fugas o aumenta RAM. No finjas que la afinación de GPU arreglará el paging del host.

Task 9: Identificar los principales consumidores de CPU (herramientas de captura en segundo plano son villanos frecuentes)

cr0x@server:~$ ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head
  PID COMMAND         %CPU %MEM
 4121 chrome          38.2  4.1
 9332 obs             22.7  1.9
 7771 game-bin        18.4  6.8
 1260 Xorg             9.2  0.6
 2104 pulseaudio       3.1  0.1

Qué significa: Tu “benchmark” compite con un navegador y una herramienta de streaming.

Decisión: Reproduce en condiciones limpias. Si OBS es requerido, trátalo como parte de la carga de producción.

Task 10: Comprobar latencia de I/O de disco (streaming de assets y cargas de modelos)

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0-21-generic (server) 	01/21/2026 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.41    0.00    3.28    2.91    0.00   81.40

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz aqu-sz  %util
nvme0n1         92.0   18240.0     0.0   0.00    3.12   198.3      44.0    5280.0     2.0   4.35    5.44   120.0   0.36  18.40

Qué significa: r_await/w_await son modestos. Si ves awaits de 50–200ms, tendrás hitches sin importar la GPU.

Decisión: Si el almacenamiento es lento, arregla el streaming (prefetch, compresión, empaquetado) antes de tocar ajustes de inferencia.

Task 11: Validar espacio en el sistema de archivos (logs y caches pueden llenar discos en plena ejecución)

cr0x@server:~$ df -h /var
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  220G  214G  6.0G  98% /

Qué significa: Estás a una sesión de logging entusiasta de un mal día.

Decisión: Libera espacio o redirige caches/logs. Un disco lleno puede romper cachés de shader, cachés de modelo y la escritura de volcados de crash.

Task 12: Inspeccionar contadores de error GPU (inestabilidad hardware/driver)

cr0x@server:~$ sudo journalctl -k -b | grep -Ei 'nvrm|gpu|amdgpu|i915' | tail
Jan 21 09:58:11 server kernel: NVRM: Xid (PCI:0000:01:00): 31, pid=7771, name=game-bin, Ch 0000002c, intr 00000000
Jan 21 09:58:11 server kernel: NVRM: GPU at PCI:0000:01:00: GPU has fallen off the bus.

Qué significa: Eso no es un problema de optimización. Es un incidente de estabilidad: reset del driver, problema de potencia o fallo de hardware.

Decisión: Deja de afinar calidad. Reproduce bajo pruebas de estrés, revisa potencia, térmicas y problemas conocidos del driver.

Task 13: Comprobar relojes de GPU y razones de throttling

cr0x@server:~$ nvidia-smi -q -d CLOCK,PERFORMANCE | sed -n '1,80p'
==============NVSMI LOG==============

Performance State                          : P2
Clocks
    Graphics                               : 2580 MHz
    Memory                                 : 9501 MHz
Clocks Throttle Reasons
    Idle                                   : Not Active
    Applications Clocks Setting            : Not Active
    SW Power Cap                           : Not Active
    HW Slowdown                            : Not Active
    HW Thermal Slowdown                    : Not Active

Qué significa: No hay throttling obvio. Si el slowdown térmico está activo durante picos, tu “regresión de IA” puede ser solo calor.

Decisión: Si aparece throttling después de minutos, prueba con curvas de ventilador fijas y flujo de caja antes de reescribir el pipeline.

Task 14: Confirmar que los archivos de modelo no se recargan repetidamente (cache thrash)

cr0x@server:~$ lsof -p $(pgrep -n game-bin) | grep -E '\.onnx|\.plan|\.bin' | head
game-bin 7771 cr0x  mem REG  259,2  31248768  1048612 /opt/game/models/upscaler_v7.plan
game-bin 7771 cr0x  mem REG  259,2   8421376  1048620 /opt/game/models/denoiser_fp16.bin

Qué significa: Los pesos del modelo están memory-mapped. Bien. Si ves patrones repetidos de open/close en trazas, estás pagando costes de carga en tiempo de ejecución.

Decisión: Precarga y fija modelos en startup o carga de nivel; no cargues perezosamente en la primera explosión.

Task 15: Comprobar comportamiento de caché de shaders (la compilación causa stutter atribuido a la IA)

cr0x@server:~$ ls -lh ~/.cache/nv/GLCache | head
total 64M
-rw------- 1 cr0x cr0x 1.2M Jan 21 09:40 0b9f6a8d0b4a2f3c
-rw------- 1 cr0x cr0x 2.8M Jan 21 09:41 1c2d7e91a1e0f4aa
-rw------- 1 cr0x cr0x 512K Jan 21 09:42 3f4a91c2d18e2b0d

Qué significa: La caché existe y está poblada. Si está vacía en cada ejecución, tu entorno la está borrando o los permisos son incorrectos.

Decisión: Asegura que las cachés de shader persistan en pruebas y producción. De lo contrario perseguirás picos de fotograma “aleatorios” para siempre.

Task 16: Medir jitter de planificación en el host (útil para pacing del hilo de render)

cr0x@server:~$ sudo cyclictest -m -Sp90 -i200 -h400 -D5s | tail -n 3
T: 0 ( 2345) P:90 I:200 C: 25000 Min:    5 Act:    7 Avg:    9 Max:  112
T: 1 ( 2346) P:90 I:200 C: 25000 Min:    5 Act:    6 Avg:    8 Max:   98
T: 2 ( 2347) P:90 I:200 C: 25000 Min:    5 Act:    6 Avg:    8 Max:  130

Qué significa: Un jitter máximo en el rango ~100µs suele estar bien. Si ves jitter de varios milisegundos, tu OS te está interrumpiendo fuerte.

Decisión: Para perfiles reproducibles, aisla CPUs, doma daemons en segundo plano y evita vecinos ruidosos (VMs, modos de potencia de laptop).

Tres minicasos corporativos desde la trinchera

Mini-caso #1: El incidente causado por una suposición errónea

Un estudio envió un parche que “solo cambió el upscaler.” Las notas de la versión decían: mayor nitidez, menos artefactos. QA aprobó la calidad visual en un set de escenas controladas y el rendimiento parecía estable en sus máquinas de laboratorio.

En cuestión de horas, llegaron tickets de soporte: tartamudeos intermitentes, principalmente en GPUs de gama media con 8–10 GiB de VRAM. Los tartamudeos no aparecían de inmediato. Surgían tras 20–30 minutos, a menudo después de un par de transiciones de mapa. El equipo culpó a la compilación de shaders. Olía a compilación de shaders.

La suposición errónea: el nuevo modelo sería “más o menos del mismo tamaño” en VRAM porque tenía resolución I/O similar. Pero la ruta de inferencia del motor activó silenciosamente un buffer intermedio de mayor precisión para el nuevo modelo. Añade un buffer de historial un poco mayor y una máscara reactiva más agresiva, y la holgura de VRAM desapareció.

En esas GPUs, el driver empezó a evictar recursos. No siempre los mismos. El patrón de evicción dependía de qué más estaba residente: texturas, estructuras de aceleración RT, shadow maps, herramientas de captura. El “stutter por shader” era en realidad churn de memoria y re-subidas ocasionales.

La solución no fue heroica: limitar la resolución del historial, forzar intermedios FP16 y reservar presupuesto de VRAM explícito para el modelo y los buffers de historial. Añadieron una advertencia en runtime cuando la holgura caía por debajo de un umbral y expusieron un upscaler en “modo seguro” que sacrificaba nitidez por estabilidad. La lección también fue aburrida: trata la VRAM como un presupuesto con guardarraíles, no como una sugerencia de mejor esfuerzo.

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

Un equipo de motor decidió “ahorrar ancho de banda” empaquetando vectores de movimiento y profundidad en un formato más compacto. El mensaje del commit fue alegre: G-buffer más pequeño, pases más rápidos, mejor localización de cache. Los benchmarks mejoraron un par de puntos en promedio. Todos aplaudieron y siguieron.

Entonces el pipeline híbrido empezó a mostrar ghosting intermitente en geometría delgada—vallas, cables, ramas—especialmente durante paneos de cámara rápidos. Solo en algunas escenas. Solo con cierta iluminación. Solo en algunas GPUs. Los reportes de bug eran vagos, porque los fotogramas se veían “mayormente bien” hasta que te fijabas lo suficiente para odiar tus propios ojos.

La optimización redujo la precisión exactamente en los lugares que el upscaler necesitaba: movimiento sub-pixel y discontinuidades de profundidad precisas. El modelo fue entrenado asumiendo cierta distribución de errores de movimiento; el nuevo empaquetado cambió la distribución. No lo suficiente para romper cada fotograma. Suficiente para romper los difíciles.

El revés fue organizacional también. El equipo mejoró una métrica (ancho de banda) mientras destruía a escondidas otra (fidelidad de entrada). Como los buffers de entrada parecían “detalles internos”, nadie actualizó la suite de validación del modelo. No había guardarraíl para “regresión en calidad de vectores de movimiento”.

Revirtieron el cambio de empaquetado para la ruta IA mientras lo mantenían para la ruta no-IA. Luego crearon un contrato: la precisión y rango de vectores de movimiento se convirtieron en entradas versionadas, con pruebas automáticas de escenas que comparaban métricas de estabilidad temporal antes y después de cambios. Aun así optimizaron—pero solo con un presupuesto de calidad en el bucle.

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

Un equipo de plataforma administraba el runtime que cargaba modelos, seleccionaba modos de precisión y negociaba con el backend gráfico. Nada llamativo. Nadie escribió posts sobre ello. Pero tenían una práctica que parecía papeleo: cada artefacto de modelo se trataba como un deployable con versionado semántico y un changelog que incluía supuestos de entrada.

Un viernes, una actualización de driver llegó a su flota interna. De repente, un subconjunto de máquinas empezó a mostrar parpadeos raros durante la generación de fotogramas—un fotograma cada pocos minutos. El parpadeo era pequeño pero obvio en el movimiento. El tipo de bug que arruina la confianza porque es lo bastante raro como para evadir reproducción rápida.

Porque los artefactos de modelo y el runtime estaban versionados y logueados por fotograma, pudieron responder la pregunta crucial en una hora: nada cambió en el modelo. El runtime cambió sólo de forma menor. El driver cambió, y sólo en las máquinas afectadas.

Activaron el kill-switch para deshabilitar la generación de fotogramas para esa rama de driver mientras dejaban el upscaling y el denoising intactos. El juego siguió siendo jugable. QA recuperó una baseline estable. Mientras tanto, trabajaron con el proveedor en un repro mínimo y lo verificaron contra la matriz fijada.

La práctica salvadora no fue genial. Fue higiene operacional aburrida: pinchar versiones, manifiestos por fotograma y controles de rollback rápidos. Convirtió un incidente potencial de fin de semana en una degradación controlada con alcance claro.

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

1) Síntoma: Trazas fantasma detrás de personajes en movimiento

Causa raíz: Vectores de movimiento erróneos para meshes skinning, partículas o animación de vértices; falta máscara de desoclusión.

Solución: Valida la velocidad para cada ruta de render; genera movimiento para partículas por separado; reinicia historial en vectores inválidos; añade máscaras reactivas.

2) Síntoma: Texto de UI se difumina o “hace eco” durante el movimiento de cámara

Causa raíz: UI compuesta antes de la reconstrucción temporal, o la UI se filtra en buffers de historial.

Solución: Componer la UI después de upscaling/denoising; asegurar que los render targets de UI estén excluidos del historial y de los pases de vectores de movimiento.

3) Síntoma: El rendimiento está bien en benchmarks, horrible después de 30 minutos

Causa raíz: Fragmentación de VRAM, crecimiento de streaming de assets, pesos de modelo evictados por presión, o throttling térmico.

Solución: Rastrear VRAM a lo largo del tiempo; imponer presupuestos; precargar y fijar asignaciones de modelo; monitorizar razones de throttling; corregir fugas en RTs transitorios.

4) Síntoma: La generación de fotogramas se siente suave pero la entrada se siente con lag

Causa raíz: Cadencia de pantalla desacoplada de la cadencia de simulación; buffering extra; modo de latencia mal configurado.

Solución: Mide input-to-photon; reduce profundidad de cola de render; afina modos de baja latencia; ofrece toggles para jugadores con descripciones honestas.

5) Síntoma: Shimmering en follaje y geometría fina

Causa raíz: Inestabilidad temporal por submuestreo más máscara reactiva insuficiente; pérdida de precisión en profundidad/velocidad; sharpening agresivo.

Solución: Mejora la precisión de entradas; afina la máscara reactiva; reduce el sharpening; limita la contribución del historial en regiones de alta frecuencia.

6) Síntoma: Fotograma negro súbito o fotograma corrupto de vez en cuando

Causa raíz: Reset del driver GPU, recuperación tipo TDR, out-of-bounds en un pase compute o ruta de fallo del runtime del modelo no manejada.

Solución: Captura logs del kernel; añade fallback robusto cuando la inferencia falle; valida límites y estados de recursos; escala como un problema de estabilidad, no de “calidad”.

7) Síntoma: “Solo ocurre en la GPU de un proveedor”

Causa raíz: Diferentes modos matemáticos, manejo de denormales, planificación o defaults de precisión; diferencias del compilador del driver.

Solución: Construye baselines por proveedor; restringe precisión; prueba por proveedor y por arquitectura; no asumas “mismo API significa mismo comportamiento”.

8) Síntoma: Artefactos aparecen tras un corte de cámara o respawn

Causa raíz: Historial no reiniciado; el modelo intenta conciliar fotogramas no relacionados.

Solución: Trata los cortes como reinicios duros; desvanece la contribución del historial; re-inicializa secuencias de exposición y jitter.

Listas de verificación / plan paso a paso

Paso a paso: enviar un fotograma medio generado sin avergonzarte

  1. Define presupuestos: tiempo de fotograma p95 y p99, objetivo de holgura de VRAM, objetivo input-to-photon. Escríbelos. Hazlos exigibles.
  2. Versiona todo: versión de modelo, versión de runtime, baseline de drivers, feature flags. Regístralos por fotograma en builds de depuración.
  3. Construye una escalera de fallback: render nativo → TAA clásico → upscaler ML → ML + generación de fotogramas. Cada paso debe ser enviable.
  4. Valida entradas: vectores de movimiento (todos los tipos de geometría), precisión de profundidad, estabilidad de exposición, manejo de alpha, detección de desoclusión.
  5. Crea una suite de pruebas temporales: paneos rápidos, follaje, tormentas de partículas, cortes de cámara, respawns, superposiciones de UI. Automatiza capturas y métricas.
  6. Reserva VRAM: presupuestar buffers de historial y pesos de modelo explícitamente; no “ver qué pasa”.
  7. Calienta: precompila shaders, pre-inicializa inferencia, pre-asigna RTs donde sea posible. Escóndelo tras pantallas de carga.
  8. Instrumenta tiempos por etapa: render base, inferencia, post, present; incluye profundidad de cola y métricas de pacing.
  9. Controla latencia de cola: limita el trabajo en el peor caso; evita asignaciones en frame; vigila la contención de CPU en segundo plano.
  10. Envía kill-switches: ops necesita toggles para desactivar FG o cambiar a un modelo más pequeño sin rebuild completo.
  11. Documenta compensaciones para el jugador: suavidad vs latencia, modos de calidad vs estabilidad. Si lo ocultas, los jugadores lo descubrirán de forma ruidosa.
  12. Ejecuta pruebas de resistencia: 2–4 horas, múltiples transiciones de mapa, rutas con mucho streaming. La mayoría de “problemas de IA” son en realidad problemas de recursos dependientes del tiempo.

Checklist: antes de culpar al modelo

  • ¿La holgura de VRAM es >10% durante las peores escenas?
  • ¿Los vectores de movimiento son válidos para cada ruta de render (skinned, partículas, animación de vértices)?
  • ¿Reinicias historial en cortes y fotogramas inválidos?
  • ¿Compuestas la UI después de etapas temporales?
  • ¿Están fijas las versiones de driver y modelo para reproducibilidad?
  • ¿Puedes reproducir con la IA deshabilitada? Si no, tu setup de medición es sospechoso.

Preguntas frecuentes

1) ¿“Fotograma medio generado” es solo marketing para upscaling?

No. El upscaling es una parte. “Medio generado” significa que el pipeline renderiza intencionalmente datos incompletos y depende de la inferencia para reconstruir o sintetizar el resto, a veces incluyendo tiempo (fotogramas generados) y a veces transporte de luz (denoising).

2) ¿La generación de fotogramas aumenta el rendimiento o solo lo oculta?

Aumenta la tasa de fotogramas mostrada, lo que puede mejorar la suavidad percibida. No aumenta la tasa de simulación y puede incrementar la latencia percibida según buffering y modos de latencia. Mide input-to-photon, no discutas en círculos.

3) ¿Cuál es el riesgo operativo #1 al añadir IA al render?

Latencia en cola y comportamiento de memoria. El tiempo medio de fotograma puede mejorar mientras que el p99 empeora debido a evicción de VRAM, warmup, planificación del driver o rutas ocasionalmente lentas.

4) ¿Por qué los artefactos suelen aparecer en follaje y geometría fina?

Esas características son de alta frecuencia y a menudo están submuestreadas. También generan fuertes desocluciones y vectores de movimiento poco fiables. La reconstrucción temporal es frágil cuando las entradas no describen el movimiento claramente.

5) ¿Podemos hacer deterministas las etapas de IA para replays?

A veces. Puedes restringir precisión, fijar semillas, evitar kernels no deterministas y fijar runtimes/drivers. Pero la determinismo entre proveedores y versiones de drivers es difícil. Si las repeticiones deterministas son un requisito de producto, diseña el pipeline con un modo determinista desde el día uno.

6) ¿Deberíamos enviar un modelo grande o varios modelos pequeños?

Varios. Quieres una escalera: alta calidad, equilibrado, seguro. Los sistemas de producción necesitan degradación elegante. Un modelo grande es un punto único de fallo con un peinado elegante.

7) ¿Cómo probamos “calidad” sin depender de capturas subjetivas?

Usa métricas temporales: varianza a lo largo del tiempo, estabilidad de bordes, heurísticas de ghosting, contadores de error de desoclusión y escenas “tortura” curadas. También mantén revisión humana, pero hazla enfocada y repetible.

8) ¿Qué debe exigir ops a los equipos de gráficos antes de habilitar la generación de fotogramas por defecto?

Un impacto de latencia medido, un kill-switch, mensajería clara al jugador y una matriz de regresión a través de versiones de drivers y hardware común. Si no pueden proveer eso, habilitar por defecto es una apuesta a la fiabilidad.

9) ¿Por qué “funciona en mi máquina” empeora con la IA?

Porque has añadido más estado oculto: cachés de modelo, modos de precisión, diferencias de planificación de driver, variación en holgura de VRAM y perfiles térmicos/potencia. El sistema es más dependiente de la ruta, lo que castiga baselines descuidados.

Conclusión: qué hacer la próxima semana

El fotograma que está medio generado ya no es un proyecto científico. Es un pipeline de producción con todos los pecados habituales: presupuestos ignorados, versiones sin fijar, cachés borrados y “optimizaciones” que eliminan las señales que el modelo necesita. La buena noticia es que las soluciones se parecen a la ingeniería normal: medición, guardarraíles y despliegues controlados.

La próxima semana, haz estas cosas prácticas:

  • Define objetivos p95/p99 y input-to-photon, y conviértelos en puertas de lanzamiento.
  • Añade manifiestos por fotograma: versiones de modelo/runtime/driver y toggles clave, registrados en builds de depuración.
  • Construye una escalera de fallback probada y conéctala a un kill-switch que ops pueda usar.
  • Rastrea la holgura de VRAM y el riesgo de paginación como una métrica de primera clase, no como una ocurrencia tardía.
  • Automatiza escenas de tortura temporales y valida vectores de movimiento como si tu trabajo dependiera de ello—porque depende.

El renderizado híbrido seguirá evolucionando. Tu trabajo es volverlo aburrido en producción: predecible, observable y recuperable cuando se comporte mal. La parte “IA” impresiona. La parte “pipeline” es donde o envías—o pasas tus fines de semana mirando gráficos de tiempo de fotograma como si fueran gráficos bursátiles.

Proxmox no detecta discos: lista rápida para HBA, BIOS y cableado

No hay nada que diga “fin de semana divertido” como arrancar un nodo Proxmox y descubrir que tus discos nuevos te han dejado plantado. El instalador no muestra nada. lsblk es un desierto. Los pools ZFS desaparecen. Juras que los discos estaban ayer.

Esta es una hoja de verificación para humanos en producción: ingenieros de almacenamiento, SREs y el desafortunado on-call que heredó una expansión de disco “simple”. Rastrearemos el dominio de fallo rápido: BIOS/UEFI, firmware y modo del HBA, PCIe, rarezas de cableado/backplanes/expanders, controladores en Linux y las trampas que hacen que los discos “existan” pero sean invisibles.

Playbook de diagnóstico rápido (hacer en este orden)

0) Decide qué significa “no detectado”

  • No en BIOS/UEFI: hardware, alimentación, cableado, backplane, enumeración HBA/PCIe.
  • En BIOS pero no en Linux: controlador/kernel módulo, peculiaridades de IOMMU, firmware roto, errores PCIe AER.
  • En Linux pero no en la UI de Proxmox: pantalla equivocada, particiones existentes, multipath ocultando, ZFS reteniendo dispositivos, permisos, o está bajo /dev/disk/by-id pero no es obvio.

1) Empieza con la verdad del kernel

Ejecuta estos tres y no improvises todavía:

  1. dmesg -T | tail -n 200 (buscar PCIe, SAS, SATA, NVMe, reinicios de enlace)
  2. lsblk -e7 -o NAME,TYPE,SIZE,MODEL,SERIAL,TRAN,HCTL (ver qué creó el kernel)
  3. lspci -nn | egrep -i 'sas|raid|sata|nvme|scsi' (confirmar que el controlador existe)

Decisión: Si el controlador no aparece en lspci, deja de culpar a Proxmox. Es BIOS/PCIe asiento/asignación de lanes o la tarjeta está muerta.

2) Si el controlador existe, verifica el driver y el enlace

  • lspci -k -s <slot> → verificar “Kernel driver in use”.
  • journalctl -k -b | egrep -i 'mpt3sas|megaraid|ahci|nvme|reset|timeout|aer' → encontrar la bala humeante.

Decisión: ¿Ningún driver enlazado? Carga el módulo o arregla firmware/configuración BIOS. ¿Reinicios/timeouts del enlace? sospecha cableado/backplane/expander/alimentación.

3) Reescanear antes de reiniciar

Reescanea SCSI/NVMe. Si los discos aparecen tras un rescan, aprendiste algo: hotplug, entrenamiento de enlace o tiempos de arranque.

4) Si los discos aparecen pero “faltan” en la UI de Proxmox

Ve al CLI y usa identificadores estables. La UI no miente; sólo no es tu comandante de incidentes.

Decisión: Si existen en /dev/disk/by-id pero no en tu pool, es una historia de ZFS/importación/particionado, no de detección.

Un modelo mental práctico: dónde pueden desaparecer los discos

La detección de discos es una cadena. Rompe cualquier eslabón y mirarás una lista vacía.

Capa 1: Alimentación y conectividad física

El disco necesita alimentación, el conector correcto y un backplane que no haga danza interpretativa. “Arranca” no es lo mismo que “enlace de datos establecido”. SAS en especial alimentará un disco mientras el enlace está caído por una lane mala.

Capa 2: Interposer/backplane/expander y su traducción

Los backplanes SAS pueden incluir expanders, multiplexores y lógica “útil”. Una sola lane marginal puede dejar caer un disco, o peor, hacerlo oscilar bajo carga. SATA detrás de expanders SAS funciona—hasta que no, dependiendo del expander, firmware del disco y cableado.

Capa 3: Firmware y modo del HBA/controlador

Los HBA pueden funcionar como HBAs reales (modo IT) o hacerse pasar por controladoras RAID (IR/modo RAID). Proxmox + ZFS quiere pase directo. La personalidad RAID puede ocultar discos detrás de volúmenes virtuales, bloquear SMART y complicar la recuperación ante errores.

Capa 4: Enumeración PCIe y presupuesto de lanes

El propio controlador es un dispositivo PCIe. Si la placa base no lo enumera, Linux tampoco podrá. La bifurcación de PCIe, el cableado del slot y el compartir lanes con M.2/U.2 pueden hacer que un slot “físico x16” sea eléctricamente x4—o x0, si enfadas a los dioses de las lanes.

Capa 5: Drivers del kernel Linux + creación de nodos

Incluso cuando el hardware está bien, el kernel puede no enlazar el driver correcto, o udev puede no crear nodos como esperas. Multipath puede ocultar intencionalmente rutas individuales. Un initramfs antiguo puede faltar módulos. Los discos pueden existir pero con nombres distintos.

Capa 6: Presentación de almacenamiento en Proxmox

Proxmox VE es Debian bajo una UI. Si Debian no lo ve, Proxmox no puede. Si Debian lo ve pero la UI no lo muestra donde buscas, es un problema de flujo de trabajo, no de hardware.

Parafraseando a John Allspaw: la fiabilidad viene de responder bien a la falla, no de fingir que no existe.

Chiste #1: “El modo RAID hará feliz a ZFS” es como decir “le puse un volante a la tostadora; ahora es un coche”.

Hechos e historia interesantes que realmente ayudan a depurar

  • El escaneo SCSI es antiguo… y sigue aquí. Las pilas SAS modernas e incluso algunas SATA todavía dependen de escaneos de host SCSI, por eso los rescans pueden “encontrar” discos sin reiniciar.
  • Los HBAs SAS de LSI se convirtieron en estándar. La línea Broadcom/Avago/LSI importa porque los nombres de drivers (mpt2sas/mpt3sas) y las herramientas de firmware siguen esa genealogía.
  • El modo IT se popularizó porque los sistemas de archivos se volvieron más inteligentes. ZFS y sistemas similares quieren visibilidad directa del disco. Las controladoras RAID se pensaron en una era donde el controlador manejaba la integridad.
  • SFF-8087 y SFF-8643 parecen “solo cables” pero son sistemas de señal. Un mini-SAS parcialmente insertado puede alimentar discos y aun así fallar en las lanes de datos. No es magia; son pares diferenciales y tolerancias.
  • Los slots PCIe mienten por marketing. “Slot x16” a menudo significa “conector x16”. Eléctricamente puede ser x8 o x4 según el CPU y la placa.
  • UEFI cambió el comportamiento de los option ROM. Algunas tarjetas de almacenamiento dependen de option ROMs para pantallas de enumeración en arranque; las configuraciones UEFI pueden ocultar esas pantallas sin cambiar lo que Linux ve.
  • NVMe trajo su propia vía de detección. Los dispositivos NVMe no son “discos SCSI” y no aparecerán en herramientas HBA SAS; usan el subsistema NVMe y el entrenamiento de enlace PCIe.
  • El paso de SMART no está garantizado. Con controladoras RAID, los datos SMART pueden estar bloqueados o requerir herramientas del proveedor, lo que cambia cómo verificas “que el disco existe”.

Tareas prácticas (comandos + significado + decisión)

Estas son las tareas que realmente ejecuto cuando un nodo dice “no hay discos”. Cada una incluye qué buscas y la decisión que tomas.

Task 1: Confirmar que el controlador está enumerado en PCIe

cr0x@server:~$ lspci -nn | egrep -i 'sas|raid|sata|scsi|nvme'
03:00.0 Serial Attached SCSI controller [0107]: Broadcom / LSI SAS3008 PCI-Express Fusion-MPT SAS-3 [1000:0097] (rev 02)
01:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd NVMe SSD Controller [144d:a808]

Qué significa: La placa base ve el HBA/controlador NVMe. Si no está aquí, Linux nunca verá los discos detrás.

Decisión: Dispositivo ausente → vuelve a insertar la tarjeta, cambia de slot, revisa configuraciones PCIe en BIOS, desactiva dispositivos en conflicto, verifica alimentación a risers.

Task 2: Verificar el enlace del driver del kernel

cr0x@server:~$ lspci -k -s 03:00.0
03:00.0 Serial Attached SCSI controller: Broadcom / LSI SAS3008 PCI-Express Fusion-MPT SAS-3 (rev 02)
	Subsystem: Broadcom / LSI SAS9300-8i
	Kernel driver in use: mpt3sas
	Kernel modules: mpt3sas

Qué significa: El driver correcto está enlazado. Si “Kernel driver in use” está vacío, tienes un problema de driver/firmware/blacklist.

Decisión: No hay driver enlazado → revisa modprobe, logs del kernel, Secure Boot, compatibilidad de firmware y si estás usando un kernel de proveedor extraño.

Task 3: Ver qué discos creó Linux (no confíes en la UI aún)

cr0x@server:~$ lsblk -e7 -o NAME,TYPE,SIZE,MODEL,SERIAL,TRAN,HCTL
NAME    TYPE  SIZE MODEL              SERIAL        TRAN HCTL
sda     disk  3.6T ST4000NM0035-1V4    ZC123ABC      sas  3:0:0:0
sdb     disk  3.6T ST4000NM0035-1V4    ZC123DEF      sas  3:0:1:0
nvme0n1 disk  1.8T Samsung SSD 990 PRO S6Z1NZ0R12345 nvme -

Qué significa: Si está en lsblk, el kernel lo ve. TRAN te dice si es sas, sata, nvme.

Decisión: Discos ausentes → bajar por la pila: dmesg, cableado, expander, alimentación. Discos presentes pero Proxmox “faltan” → probablemente UI/flujo, multipath o importación ZFS.

Task 4: Revisar logs del kernel por reinicios/timeouts de enlace

cr0x@server:~$ journalctl -k -b | egrep -i 'mpt3sas|megaraid|ahci|nvme|reset|timeout|aer|link down' | tail -n 60
Dec 26 10:12:01 server kernel: mpt3sas_cm0: log_info(0x31120101): originator(PL), code(0x12), sub_code(0x0101)
Dec 26 10:12:01 server kernel: sd 3:0:1:0: rejecting I/O to offline device
Dec 26 10:12:03 server kernel: pcieport 0000:00:1c.0: AER: Corrected error received: 0000:03:00.0
Dec 26 10:12:03 server kernel: nvme nvme0: I/O 42 QID 5 timeout, aborting

Qué significa: “offline device”, “timeout”, “link down”, spam AER = integridad de señal, alimentación, o dispositivo/controlador fallando.

Decisión: Timeouts en múltiples discos → cable/backplane/expander/HBA. Timeouts en un disco → ese disco o su slot.

Task 5: Listar controladoras de almacenamiento que el kernel reconoce

cr0x@server:~$ lsscsi -H
[0]    ata_piix
[2]    mpt3sas
[3]    nvme

Qué significa: Confirma adaptadores host. Si el driver del HBA está cargado, aparece como host.

Decisión: HBA ausente aquí pero presente en lspci → el driver no cargó o falló al inicializarse.

Task 6: Inspeccionar hosts SCSI y reescanear dispositivos

cr0x@server:~$ ls -l /sys/class/scsi_host/
total 0
lrwxrwxrwx 1 root root 0 Dec 26 10:10 host0 -> ../../devices/pci0000:00/0000:00:17.0/ata1/host0/scsi_host/host0
lrwxrwxrwx 1 root root 0 Dec 26 10:10 host2 -> ../../devices/pci0000:00/0000:03:00.0/host2/scsi_host/host2
cr0x@server:~$ for h in /sys/class/scsi_host/host*/scan; do echo "- - -" > "$h"; done

Qué significa: Fuerza un escaneo de todos los hosts SCSI. Si los discos aparecen después, la detección es por tiempo/hotplug/comportamiento del expander.

Decisión: Si los rescans consistentemente “lo arreglan”, revisa hotplug del BIOS, spin-up escalonado, firmware del expander y firmware del HBA.

Task 7: Comprobar detección SATA/AHCI (puertos integrados)

cr0x@server:~$ dmesg -T | egrep -i 'ahci|ata[0-9]|SATA link' | tail -n 40
[Thu Dec 26 10:10:12 2025] ahci 0000:00:17.0: AHCI 0001.0301 32 slots 6 ports 6 Gbps 0x3f impl SATA mode
[Thu Dec 26 10:10:13 2025] ata1: SATA link down (SStatus 0 SControl 300)
[Thu Dec 26 10:10:13 2025] ata2: SATA link up 6.0 Gbps (SStatus 133 SControl 300)

Qué significa: “link down” en un puerto con disco significa cableado/puerto deshabilitado en BIOS/alimentación.

Decisión: Si los puertos están link down en todo el tablero, revisa modo SATA en BIOS (AHCI), y si la placa deshabilita SATA cuando hay M.2 poblado.

Task 8: Enumerar dispositivos NVMe y estado del controlador

cr0x@server:~$ nvme list
Node             SN               Model                          Namespace Usage                      Format           FW Rev
---------------- ---------------- -------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1      S6Z1NZ0R12345    Samsung SSD 990 PRO 2TB        1         1.80  TB / 2.00  TB        512   B +  0 B   5B2QJXD7

Qué significa: NVMe está presente como su propio subsistema. Si nvme list está vacío pero lspci muestra el controlador, puede ser driver, ASPM de PCIe o problemas de enlace.

Decisión: Lista vacía → revisar journalctl -k para errores NVMe, configuraciones BIOS de velocidad PCIe y bifurcación de slots (para adaptadores multi-NVMe).

Task 9: Confirmar identificadores estables de disco (lo que debes usar para ZFS)

cr0x@server:~$ ls -l /dev/disk/by-id/ | egrep -i 'wwn|nvme|scsi' | head
lrwxrwxrwx 1 root root  9 Dec 26 10:15 nvme-Samsung_SSD_990_PRO_2TB_S6Z1NZ0R12345 -> ../../nvme0n1
lrwxrwxrwx 1 root root  9 Dec 26 10:15 scsi-35000c500a1b2c3d4 -> ../../sda
lrwxrwxrwx 1 root root  9 Dec 26 10:15 scsi-35000c500a1b2c3e5 -> ../../sdb
lrwxrwxrwx 1 root root  9 Dec 26 10:15 wwn-0x5000c500a1b2c3d4 -> ../../sda

Qué significa: Estos IDs sobreviven a reinicios y renombres de dispositivo (sda que pasa a sdb tras cambios de hardware).

Decisión: Si tus scripts de pool/import usan /dev/sdX, deja de hacerlo. Migra a by-id/by-wwn antes de tu próxima ventana de mantenimiento te coma.

Task 10: Comprobar visibilidad SMART (te dice si realmente ves el disco)

cr0x@server:~$ smartctl -a /dev/sda | head -n 20
smartctl 7.3 2022-02-28 r5338 [x86_64-linux-6.8.12-4-pve] (local build)
=== START OF INFORMATION SECTION ===
Model Family:     Seagate Exos 7E8
Device Model:     ST4000NM0035-1V4
Serial Number:    ZC123ABC
LU WWN Device Id: 5 000c50 0a1b2c3d4
Firmware Version: SN03
User Capacity:    4,000,787,030,016 bytes [4.00 TB]

Qué significa: Si SMART funciona, probablemente tienes visibilidad de passthrough real. Si SMART falla detrás de una controladora RAID, puede que necesites otro tipo de dispositivo o utilidades del proveedor.

Decisión: SMART bloqueado + quieres ZFS → verifica HBA en modo IT o HBA real, no personalidad RAID.

Task 11: Detectar si multipath está ocultando tus discos

cr0x@server:~$ multipath -ll
mpatha (3600508b400105e210000900000490000) dm-0 IBM,2810XIV
size=1.0T features='1 queue_if_no_path' hwhandler='0' wp=rw
|-+- policy='service-time 0' prio=50 status=active
| `- 3:0:0:0 sda 8:0  active ready running
`-+- policy='service-time 0' prio=10 status=enabled
  `- 4:0:0:0 sdb 8:16 active ready running

Qué significa: Multipath puede presentar discos como /dev/dm-* y los /dev/sdX crudos son solo paths.

Decisión: Si son discos locales, multipath es una mala configuración. Desactívalo o ignora HBAs locales, de lo contrario Proxmox/ZFS puede ver dispositivos “extraños”.

Task 12: Identificar si estás en una controladora RAID con discos virtuales

cr0x@server:~$ lspci -nn | egrep -i 'megaraid|raid'
04:00.0 RAID bus controller [0104]: Broadcom / LSI MegaRAID SAS-3 3108 [1000:005d] (rev 02)
cr0x@server:~$ lsblk -o NAME,TYPE,SIZE,MODEL
NAME  TYPE  SIZE MODEL
sda   disk  7.3T LSI MR9361-8i

Qué significa: Si el modelo parece la controladora y solo ves un “disco”, estás viendo un disco virtual, no discos físicos individuales.

Decisión: Para ZFS: o cambias a un HBA / flasheas a modo IT, o aceptas que ZFS no podrá gestionar discos individuales correctamente.

Task 13: Comprobar si el BIOS deshabilitó el slot o remapeó lanes

cr0x@server:~$ dmidecode -t baseboard | egrep -i 'Manufacturer|Product|Version'
Manufacturer: Supermicro
Product Name: X11SPH-NCTF
Version: 1.02
cr0x@server:~$ lspci -vv -s 03:00.0 | egrep -i 'LnkCap|LnkSta'
LnkCap: Port #0, Speed 8GT/s, Width x8
LnkSta: Speed 2.5GT/s (downgraded), Width x8

Qué significa: Link degradado a 2.5GT/s sugiere problemas de integridad de señal, slot de generación equivocada forzando, o riser/cable malo.

Decisión: Links degradados con errores → intenta forzar Gen3/Gen4 en BIOS, cambia de slot, reemplaza riser, revisa el asiento físico.

Task 14: Específico de Proxmox: confirmar kernel y módulos

cr0x@server:~$ uname -r
6.8.12-4-pve
cr0x@server:~$ modinfo mpt3sas | egrep -i 'filename|version|firmware'
filename:       /lib/modules/6.8.12-4-pve/kernel/drivers/scsi/mpt3sas/mpt3sas.ko
version:        44.100.00.00
firmware:       mpt3sas_fw.bin

Qué significa: Confirma que usas el kernel de Proxmox y el módulo existe. Kernels/initramfs descoordinados pueden morder tras actualizaciones.

Decisión: Si falta el módulo o el kernel es incorrecto, arregla paquetes y regenera initramfs antes de perseguir fantasmas de hardware.

HBA, BIOS/UEFI y PCIe: la escena del crimen habitual

Modo HBA: IT vs IR/RAID (y por qué a Proxmox le importa)

Si estás usando ZFS (y muchas instalaciones Proxmox lo hacen), quieres que el HBA presente cada disco físico directamente a Linux. Eso es el modo IT en términos LSI/Broadcom. El modo RAID (IR) es otra filosofía: el controlador abstrae discos en volúmenes lógicos. Esa abstracción rompe varias cosas de las que dependes en operaciones modernas:

  • SMART/estado preciso por disco (a menudo bloqueado o extraño).
  • Identidades de disco predecibles (WWNs pueden ocultarse o reemplazarse).
  • Superficies de error claras (los timeouts pueden convertirse en “el controlador dice no”).
  • La capacidad de ZFS para gestionar redundancia y autocorrección con visibilidad completa.

Además: las controladoras RAID suelen tener cachés de escritura, BBUs y políticas que son geniales hasta que no lo son. ZFS ya hace su propia historia de consistencia. No necesitas dos capitanes dirigiendo un barco. Te marearás.

Configuraciones UEFI que impactan silenciosamente la detección

BIOS/UEFI puede ocultar o romper tu almacenamiento sin mensajes dramáticos. Las configuraciones más comunes a auditar cuando los discos desaparecen:

  • Modo SATA: AHCI vs RAID. En servidores, RAID puede enrutar puertos por una capa tipo Intel RST que Linux puede no manejar como esperas.
  • Configuración de slot PCIe: velocidad Gen forzada vs auto; bifurcación x16 → x4x4x4x4 para adaptadores multi-NVMe.
  • Política de option ROM: solo UEFI vs Legacy. Esto afecta sobre todo la visibilidad de arranque y pantallas de gestión, pero una mala configuración puede ocultar lo que esperas ver en pre-boot.
  • IOMMU/VT-d/AMD-Vi: No suele romper la detección de discos, pero puede cambiar el comportamiento de dispositivos con setups de passthrough.
  • Deshabilitado de almacenamiento onboard: Algunas placas deshabilitan puertos SATA cuando hay M.2 ocupadas, o comparten lanes con slots PCIe.

Compartir lanes PCIe: el “por qué mi slot dejó de funcionar” moderno

Las placas base son los agentes de tráfico. Mete un NVMe en una ranura M.2 y tu HBA puede caer de x8 a x4, o el slot adyacente puede deshabilitarse. Esto no es “mal diseño”. Es economía y física: los CPUs tienen lanes finitas, y los fabricantes multiplexan de formas que requieren leer la letra pequeña.

Si ves un controlador presente pero inestable (errores AER, link down/up), problemas de lane o integridad de señal están muy en juego. Los risers, especialmente, adoran estar “casi bien”.

Chiste #2: Un riser PCIe que “funciona si no tocas el chasis” es menos un componente y más una elección de estilo de vida.

Cableado, backplanes, expanders y las mentiras de “está bien insertado”

Conectores Mini-SAS: por qué la falla parcial es común

Los cables SAS transportan múltiples lanes. Un solo SFF-8643 puede llevar cuatro lanes SAS; un backplane puede mapear lanes a bahías individuales. Si una lane falla, no siempre pierdes todos los discos. Pierdes “algunas bahías”, a menudo en un patrón que parece software.

Regla práctica: si faltan discos en un patrón repetitivo de bahías (por ejemplo, bahías 1–4 bien, 5–8 muertas), sospecha de un cable mini-SAS o puerto específico. No pases una hora en udev por un problema que vive en cobre.

Backplanes con expanders: geniales cuando funcionan

Los expanders permiten conectar muchos discos a menos puertos HBA. También añaden una capa que puede tener bugs de firmware, peculiaridades de negociación y sensibilidad con discos SATA detrás de expanders SAS. Los síntomas incluyen:

  • Los discos aparecen después del arranque pero desaparecen bajo carga.
  • Mensajes intermitentes de “device offlined”.
  • Sólo algunos modelos de disco se comportan mal.

Cuando eso ocurre, no “tunees Linux”. Valida el firmware del expander, cambia cables, aisla conectando menos bahías y prueba con un modelo de disco conocido bueno.

Alimentación y spin-up

Especialmente en chasis densos, la alimentación puede ser el asesino silencioso. Los discos pueden girar pero sufrir caídas de tensión durante el entrenamiento de enlace o cuando muchos discos arrancan simultáneamente. Algunos HBAs y backplanes soportan spin-up escalonado. Otros no. Algunos lo soportan y vienen mal configurados.

Una señal reveladora es múltiples discos cayendo al mismo tiempo durante el arranque o scrub, luego reapareciendo más tarde. Eso no es cosa de “Proxmox”. Es alimentación o señal.

Comprobaciones físicas simples que superan a la inteligencia

  • Vuelve a insertar ambos extremos de los cables mini-SAS. No “presiones suavemente”. Desconecta, inspecciona, reconecta firmemente.
  • Intercambia cables entre puertos conocidos buenos y sospechosos para ver si el problema sigue al cable.
  • Mueve un disco a otra bahía. Si el disco funciona en otro sitio, la bahía/lane del backplane es sospechosa.
  • Si puedes, conecta temporalmente un disco directamente a un puerto HBA (evita el expander/backplane) para aislar capas.

Capa Linux/Proxmox: controladores, udev, multipath y nodos de dispositivo

La presencia del driver no es salud del driver

Ver mpt3sas cargado no garantiza que el controlador se inicializara correctamente. Incompatibilidades de firmware pueden producir funcionalidad parcial: el controlador se enumera, pero no aparecen targets; o los targets aparecen pero fallan constantemente.

Los logs del kernel importan más que las listas de módulos. Si ves reinicios repetidos, “firmware fault” o colas atascadas, trátalo como un incidente real: recoge logs, estabiliza hardware y considera actualizaciones de firmware.

Multipath: útil hasta que no lo es

Multipath está diseñado para SANs y almacenamiento con rutas duales. En un nodo Proxmox con discos SAS locales, suele ser accidental y dañino. Puede ocultar los dispositivos que esperas, o crear nodos device-mapper que Proxmox/ZFS usen de forma inconsistente si no eres deliberado.

Si no usas multipath explícitamente para almacenamiento compartido, por lo general quieres desactivarlo o configurarlo para ignorar discos locales.

Nombrado de dispositivos: /dev/sdX es una trampa

Linux asigna nombres /dev/sdX en orden de descubrimiento. Añade un controlador, reordena cables o cambia configuraciones de arranque y el orden cambia. Así es como importas discos equivocados, borras el dispositivo incorrecto o construyes un pool con miembros erróneos.

Usa /dev/disk/by-id o WWNs. Hazlo política. Tu yo futuro te lo agradecerá en silencio.

Cuando Proxmox “no muestra discos” pero Linux sí

Realidades comunes:

  • Los discos tienen particiones antiguas y la UI de Proxmox filtra lo que considera “disponible”.
  • ZFS ya está usando los discos (pertenecen a un pool importado o a un pool huérfano). ZFS no comparte amablemente.
  • Estás mirando en el lugar equivocado: discos del nodo vs definiciones de almacenamiento vs vista del datacenter.
  • Multipath o device-mapper presentan nombres diferentes a los que esperas.

Ángulo ZFS: por qué el “modo RAID” no es tu amigo

Proxmox incluye soporte ZFS de primera clase. ZFS asume que él está a cargo de la redundancia, checksums y curación. La RAID por hardware asume ella está a cargo de la redundancia y recuperación ante errores. Cuando apilas ambas, creas un sistema donde cada capa toma decisiones sin la información completa.

Lo que “funciona” pero sigue siendo incorrecto

  • Crear un gran volumen RAID0/RAID10 y poner ZFS encima: ZFS pierde visibilidad por disco y no puede aislar miembros que fallan.
  • Usar caché de controladora con ZFS y writes sync: puedes mentirle a ZFS sobre durabilidad si la política de caché es insegura.
  • Asumir que el controlador mostrará errores de disco claramente: puede remapear, reintentar u ocultar hasta que ya no pueda.

Qué deberías hacer en su lugar

  • Usa un HBA (o flashea el controlador a modo IT) y presenta discos crudos a ZFS.
  • Usa identificadores estables al crear pools.
  • Prefiere combinaciones de firmware aburridas y bien probadas. Lo bleeding edge está bien para laboratorio, no para tu quórum de clúster.

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

1) Síntoma: HBA no aparece en lspci

Causa raíz: Tarjeta no insertada, slot muerto, compartir lanes deshabilitó el slot, riser fallando o BIOS deshabilitó ese slot.

Solución: Reinsertar, probar otro slot, quitar riser, comprobar “PCIe slot enable” en BIOS, revisar sharing de lanes con M.2/U.2, actualizar BIOS si está anticuado.

2) Síntoma: HBA en lspci pero no hay discos en lsblk

Causa raíz: Driver no enlazado, incompatibilidad de firmware, HBA en modo que requiere stack de proveedor, cable/backplane roto impidiendo descubrimiento de targets.

Solución: Verificar lspci -k, revisar journalctl -k, reescanear hosts SCSI, cambiar cables, validar firmware y modo del HBA (IT para ZFS).

3) Síntoma: Algunas bahías faltan en un patrón

Causa raíz: Una lane/cable/puerto SAS caída; el mapeo del backplane coincide con el conjunto faltante.

Solución: Intercambiar cable mini-SAS; mover al otro puerto HBA; reinsertar el conector; comprobar pines doblados/daños.

4) Síntoma: Los discos aparecen tras un rescan pero desaparecen después de reiniciar

Causa raíz: Timing de hotplug, peculiaridades del expander, spin-up escalonado mal configurado, alimentación marginal en el arranque.

Solución: Actualizar firmware HBA/backplane/expander, habilitar spin-up escalonado si está soportado, verificar PSU y distribución de energía, revisar logs de arranque por resets.

5) Síntoma: NVMe no detectado, pero funciona en otra máquina

Causa raíz: Slot deshabilitado por configuraciones de bifurcación, PCIe Gen forzado muy alto/bajo, compartir lanes con SATA, o el adaptador necesita bifurcación.

Solución: Establecer bifurcación correcta, poner velocidad PCIe en Auto/Gen3/Gen4 según corresponda, mover a slot conectado al CPU, actualizar BIOS.

6) Síntoma: La GUI de Proxmox no muestra discos, pero lsblk

Causa raíz: Particiones existentes/metadata LVM, ZFS ya los reclama, presentación por multipath, o estás viendo la vista equivocada de la UI.

Solución: Usa CLI para confirmar by-id, revisa zpool status/zpool import, mira multipath -ll, borra firmas solo cuando estés seguro.

7) Síntoma: SMART falla con “cannot open device” detrás de la controladora

Causa raíz: Abstracción de controladora RAID; el paso de SMART requiere tipo de dispositivo especial o no está soportado.

Solución: Usar HBA/modo IT para ZFS; de lo contrario usar herramientas del proveedor y aceptar las limitaciones.

8) Síntoma: Discos oscilan bajo carga, ZFS detecta errores de checksum

Causa raíz: Integridad de señal en cable/backplane/expander o alimentación insuficiente; a veces un disco está envenenando el bus.

Solución: Reemplaza cables primero, aísla quitando discos, revisa dmesg por resets, valida salud de PSU y del backplane.

Listas de verificación / plan paso a paso

Checklist A: “El instalador no ve discos”

  1. Entra en BIOS/UEFI y confirma que el controlador está habilitado y visible.
  2. Confirma que el modo SATA es AHCI (a menos que necesites RAID explícitamente para un volumen de arranque).
  3. Para HBA: verifica que esté en modo IT o HBA real (no volúmenes virtuales MegaRAID) si quieres ZFS.
  4. Mueve el HBA a otro slot PCIe (preferir slots conectados al CPU).
  5. Arranca un entorno rescue y ejecuta lspci y dmesg. Si falta allí, es hardware.
  6. Cambia cables mini-SAS y vuelve a insertar conectores en ambos extremos.
  7. Si usas backplane con expander: prueba una conexión directa con un disco.

Checklist B: “Algunos discos faltan detrás del HBA”

  1. Ejecuta lsblk e identifica qué bahías faltan; busca patrones.
  2. Revisa logs por reinicios de enlace y dispositivos offline.
  3. Reescanea hosts SCSI; mira si aparecen los discos faltantes.
  4. Cambia el cable que alimenta el conjunto de bahías faltantes.
  5. Mueve el cable a otro puerto HBA; mira si el conjunto faltante se mueve.
  6. Mueve un disco faltante a una bahía conocida buena; si aparece, la bahía/lane es mala.
  7. Actualiza firmware del HBA si corres una versión conocida problemática.

Checklist C: “Discos detectados en Linux pero no utilizables en Proxmox”

  1. Confirma IDs estables en /dev/disk/by-id.
  2. Comprueba si ZFS ve un pool importable: zpool import.
  3. Revisa si los discos tienen firmas: wipefs -n /dev/sdX (el -n es la bandera de seguridad; úsala).
  4. Revisa multipath: multipath -ll.
  5. Decide tu intención: importar datos existentes vs borrar y reutilizar.
  6. Si vas a borrar, hazlo deliberadamente y documenta qué WWNs borraste.

Checklist D: “NVMe no aparece”

  1. Confirma el controlador en lspci.
  2. Revisa nvme list y logs del kernel por timeouts.
  3. Inspecciona estado del enlace PCIe (LnkSta) por degradaciones.
  4. Configura la bifurcación correcta para adaptadores multi-NVMe.
  5. Mueve el NVMe a otro slot y vuelve a probar.

Tres micro-historias corporativas desde las trincheras

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

El equipo estaba desplegando un nuevo clúster Proxmox para cargas internas de CI. El plan de almacenamiento era “simple”: ocho discos SAS por nodo, espejos ZFS, listo. Compras entregaron servidores con una “controladora SAS RAID” en lugar del HBA solicitado. Nadie se alarmó porque la controladora todavía tenía “SAS” en el nombre y la BIOS mostraba un enorme disco lógico.

Instalaron Proxmox en ese volumen lógico y construyeron pools ZFS sobre lo que la controladora exponía. Funcionó unas semanas, que es como las malas suposiciones se promueven a “decisiones de diseño”. Entonces un disco empezó a fallar. La controladora remapeó y reintentó de formas que ZFS no podía observar, y el nodo empezó a atascarse durante scrubs. Los logs estaban llenos de timeouts pero nada que mapease claramente a una bahía física.

Durante la ventana de mantenimiento, alguien sacó el disco “fallado” según la UI de la controladora. El equivocado. La controladora había cambiado su numeración interna tras remapeos previos, y la hoja de mapeo estaba obsoleta. Ahora el volumen lógico se degradó de otra forma, ZFS se enfureció y el clúster perdió capacidad durante carga pico.

La solución fue poco glamorosa: reemplazar la controladora RAID por un HBA real, reconstruir el nodo y aplicar una política: ZFS obtiene discos crudos, identificados por WWN, y el mapeo de bahías se valida con LEDs y números de serie antes de extraer hardware. La suposición “SAS equivale a HBA” fue la causa raíz y les costó un fin de semana.

Micro-historia 2: La optimización que salió mal

Otro equipo tuvo problemas de rendimiento durante resilvers de ZFS. Alguien sugirió “optimizar el cableado” usando un solo backplane con expander para reducir puertos HBA y mantener el montaje ordenado. Menos cables, menos puntos de fallo, ¿cierto?

En la práctica, el expander introdujo un comportamiento sutil: durante I/O intenso, un par de SSDs SATA (usados como vdevs especiales) se desconectaban intermitentemente por segundos y luego volvían. El HBA y el kernel registraban reinicios de enlace, y ZFS marcaba dispositivos como faulted o degradados según el timing. El síntoma parecía “ZFS inestable” porque las caídas eran transitorias.

El equipo intentó ajustar timeouts y profundidades de cola, porque a los ingenieros les gustan los potenciómetros y el expander parecía “enterprise”. El ajuste redujo errores obvios pero no resolvió el problema subyacente. Bajo un incidente real—reinicio del nodo más recuperación simultánea de VMs—los dispositivos volvieron a oscilar y el pool no quiso importarse sin intervención manual.

Revirtieron la “optimización”. Conectaron directamente los SSDs sensibles, dejaron el expander para los HDDs masivos donde la latencia importaba menos, y estandarizaron modelos de disco detrás del expander. El rendimiento y la tranquilidad mejoraron. A veces menos cables son solo menos pistas cuando se rompe algo.

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

Un equipo tenía la costumbre pedante: cada disco se registraba por WWN y ubicación de bahía en la instalación. Mantenían una hoja simple: serial del chasis, número de bahía, serial del disco, WWN y la pertenencia prevista al vdev ZFS. También etiquetaban cables por puerto HBA y conector de backplane. Nadie disfrutaba hacerlo, pero era política.

Un año después, un nodo reportó errores de checksum intermitentes durante scrubs. Los logs sugerían un enlace inestable, no un disco fallando, pero la topología del pool incluía doce discos y un expander de backplane. En el mundo sin inventario, eso habría degenerado en “sacar discos hasta que paren los errores”. Así se crean nuevos incidentes.

En su lugar, correlacionaron el WWN afectado con la bahía. Los errores siempre estaban en discos de las bahías 9–12. Eso coincidía con un único cable mini-SAS que alimentaba esa sección del backplane. Cambiaron el cable en una ventana corta y el scrub posterior eliminó los errores.

Sin drama. Sin adivinanzas. La práctica de inventario aburrida convirtió un incidente potencialmente complicado en una reparación de 20 minutos con causa raíz clara. La fiabilidad suele ser simplemente llevar registros con convicción.

Preguntas frecuentes

1) El instalador de Proxmox no muestra discos. ¿Siempre es problema de driver HBA?

No. Si lspci no muestra el controlador, es BIOS/PCIe/hardware. Si el controlador aparece pero no hay discos, puede ser driver/firmware/cableado.

2) Veo discos en BIOS pero no en Linux. ¿Cómo es posible?

La BIOS puede mostrar volúmenes lógicos RAID o un resumen del controlador sin exponer targets a Linux. O Linux carece del módulo correcto, o el controlador falla al inicializar durante el arranque (revisa journalctl -k).

3) ¿Necesito modo IT para Proxmox?

Si usas ZFS y quieres operaciones sensatas, sí. Si insistes en RAID por hardware, puedes usarlo, pero eliges un modelo operativo distinto con herramientas distintas.

4) ¿Por qué los discos aparecen como /dev/dm-0 en vez de /dev/sda?

Usualmente multipath o device-mapper (LVM, dm-crypt). Para discos locales que no pretendes multipath, corrige la configuración de multipath o desactívalo.

5) Mis discos aparecen, pero la GUI de Proxmox no los lista como disponibles. ¿Están rotos?

A menudo tienen firmas existentes (ZFS/LVM/RAID) o ya forman parte de un pool importado. Verifica con lsblk, wipefs -n y zpool import antes de hacer algo destructivo.

6) ¿Un cable SAS malo puede realmente causar que sólo un disco desaparezca?

Sí. Mini-SAS lleva múltiples lanes; según el mapeo del backplane, una falla de lane puede aislar una bahía o un subconjunto. Los patrones te ayudan a diagnosticar.

7) NVMe no detectado: ¿cuál es el error de BIOS más común?

Configuraciones de bifurcación incorrectas al usar adaptadores multi-NVMe, o compartir lanes que deshabilitan el slot cuando otra M.2/U.2 está poblada.

8) ¿Debo forzar la velocidad PCIe Gen para arreglar problemas de enlace?

A veces forzar una velocidad Gen inferior estabiliza enlaces inestables (útil para diagnóstico), pero la solución real suele ser asiento, risers, cableado o elección de slot en la placa.

9) ¿Cómo decido entre “reemplazar disco” y “reemplazar cable/backplane”?

Si múltiples discos muestran errores en el mismo puerto HBA/segmento de backplane, sospecha cable/backplane. Si un disco sigue a sí mismo a través de bahías, es el disco.

10) ¿Es seguro reescanear hosts SCSI en un nodo en producción?

Generalmente sí, pero con conciencia situacional. Los rescans pueden disparar eventos de descubrimiento y mucho ruido en los logs. Evítalos durante operaciones de almacenamiento sensibles si ya estás degradado.

Conclusión: próximos pasos prácticos

Si Proxmox no puede ver discos, deja de adivinar y recorre la cadena: enumeración PCIe → enlace del driver → estabilidad del enlace → descubrimiento de targets → IDs estables → consumo por Proxmox/ZFS. Las victorias más rápidas suelen ser físicas: asiento, asignación de lanes y cables. Los fallos más caros vienen del modo de controlador equivocado y del nombrado de dispositivos descuidado.

  1. Ejecuta el playbook de diagnóstico rápido y clasifica el dominio de fallo en 10 minutos.
  2. Recoge evidencias: lspci -k, lsblk y logs del kernel alrededor del tiempo de detección.
  3. Estandariza: HBA/modo IT para ZFS, nombrado por-id y un mapa de bahía a WWN.
  4. Arregla la causa raíz, no el síntoma: reemplaza cables/risers sospechosos, corrige bifurcación BIOS, actualiza firmware con criterio.
  5. Tras la recuperación, haz un scrub/resilver de prueba y revisa logs. Si no verificas, no lo arreglaste—solo dejaste de verlo.

MySQL vs PostgreSQL en un VPS de 4 GB RAM: qué configurar primero para sitios web

Tienes un VPS con 4 GB de RAM. Unos cuantos sitios web. Una base de datos. Y ahora un pager, un ticket o un correo de cliente que dice: “El sitio va lento.” Nada es más humillante que ver una máquina de $10/mes intentar ser una plataforma empresarial porque alguien activó un plugin que “solo ejecuta una consulta”.

Esta es una guía de campo para dejar MySQL o PostgreSQL lo suficientemente estable y rápido para cargas de trabajo de sitios web en hardware de VPS pequeño. No es una fantasía de benchmarks. No es un volcado de configuraciones. Lo que configuras primero, lo que mides primero y lo que dejas de hacer antes de que te cueste fines de semana.

Primera decisión: MySQL o PostgreSQL para sitios web en 4GB

En un VPS de 4GB, la “mejor base de datos” es la que puedes mantener predecible bajo presión de memoria y tráfico con ráfagas. Tu enemigo no es el rendimiento teórico. Son las tormentas de swap, las avalanchas de conexiones y los picos de latencia de almacenamiento que convierten “suficientemente rápido” en “¿por qué la compra está agotándose?”.

Elige MySQL (InnoDB) cuando:

  • Tu stack ya es nativo de MySQL (WordPress, Magento, muchas apps PHP) y no quieres ser la persona que reescribe todo “por diversión”.
  • Quieres una historia de caché relativamente directa: el InnoDB buffer pool es el gran control y se comporta como tal.
  • Necesitas replicación fácil de operar con herramientas comunes y aceptas compensaciones de consistencia eventual en algunos modos.

Elige PostgreSQL cuando:

  • Te importan la corrección de consultas y las características SQL ricas (funciones de ventana reales, CTE, mejores constraints y tipos de datos) y realmente las vas a usar.
  • Quieres planes de consulta predecibles, buena observabilidad y valores por defecto sensatos para muchos patrones de apps modernas.
  • Puedes comprometerte con el pooling de conexiones (pgBouncer) porque el modelo process-per-connection de PostgreSQL castiga “abrir más conexiones” en máquinas pequeñas.

Si esto es mayormente tráfico de CMS con plugins que no controlas, suelo ser conservador: quédate con MySQL a menos que la app ya sea Postgres-first. Si estás construyendo algo nuevo con un equipo que escribe SQL intencionalmente, PostgreSQL suele ser la mejor apuesta a largo plazo. Pero en 4GB, la victoria a corto plazo es la simplicidad operativa, no la pureza filosófica.

Regla práctica: si no puedes describir tus 5 consultas principales y sus índices, no estás “eligiendo una base de datos”, estás eligiendo qué modos de fallo quieres experimentar primero.

Hechos interesantes y contexto histórico (que realmente cambia decisiones)

  1. El dominio temprano de MySQL en la web vino por la ubicuidad del stack LAMP y la velocidad “suficientemente buena” para sitios principalmente de lectura. Por eso tantas apps de sitio aún asumen particularidades del dialecto MySQL.
  2. InnoDB se convirtió en el motor por defecto en MySQL 5.5 (era 2010). Si aún piensas en términos de MyISAM (bloqueos de tabla, sin recuperación tras crash), llevas un fósil en el bolsillo.
  3. El modelo MVCC de PostgreSQL es una de las razones por las que se mantiene consistente bajo concurrencia, pero genera la necesidad constante de vacuum. Ignorar vacuum no hará que la base de datos grite; irá empeorando lentamente.
  4. PostgreSQL ha evolucionado hacia un modelo de ejecución más amigable con el paralelismo (consultas paralelas, mejores capacidades del planner). En un VPS pequeño esto importa menos que en hierro grande, pero forma parte de por qué Postgres “se siente moderno” para consultas de analítica.
  5. El query cache de MySQL fue eliminado en MySQL 8.0 porque escalaba mal bajo concurrencia. Si alguien te dice “habilita query_cache_size”, encontraste a un viajero en el tiempo.
  6. Postgres recibe crédito por estándares y corrección porque históricamente priorizó funciones e integridad sobre velocidad cruda temprana. Hoy también es rápido, pero el ADN cultural sigue en los defaults y las herramientas.
  7. Ambos motores son conservadores respecto a durabilidad por defecto (fsync, WAL/redo). Desactivar ajustes de durabilidad hace que los benchmarks luzcan heroicos y los postmortems parezcan escenas de crimen.
  8. MariaDB se desvió de MySQL de maneras significativas. El consejo de “tuning MySQL” a veces no aplica bien a versiones de MariaDB y a sus motores de almacenamiento. Verifica qué estás ejecutando.
  9. RDS y servicios gestionados influyeron en la folclore de tuning: la gente copia defaults de la nube a VPS, y luego se pregunta por qué una caja de 4GB se comporta como si estuviera bajo el agua.

Arquitectura base para un VPS de 4GB (y por qué importa)

En un VPS de 4GB no tienes “memoria extra”. Tienes un presupuesto. Gástalo en cachés que reduzcan I/O y en margen que prevenga swapping. La caché de página del SO también importa porque tanto MySQL como PostgreSQL al final necesitan lecturas respaldadas por el sistema de ficheros, y el kernel no es tu enemigo; es tu última línea de defensa.

Presupuesto de memoria basado en la realidad

  • SO + SSH + daemons básicos: 300–600MB
  • Servidor web + PHP-FPM: variable. Unos cientos de MB hasta varios GB según el número de procesos y el comportamiento de la app.
  • Base de datos: lo que queda, pero no todo. Si le das al DB todo, la capa web hará OOM o swapeará cuando suba el tráfico.

Para “sitios web en un único VPS”, la base de datos no está aislada. Esta es una de las pocas veces en que “configurar y olvidar” no es pereza; es supervivencia.

Opinión: Si ejecutas web y DB en el mismo VPS de 4GB, planea asignar aproximadamente máximo 1.5–2.5GB a la capa de caché de la base de datos, a menos que hayas medido el uso de memoria de PHP bajo carga y sea realmente pequeño. Tu objetivo es latencia estable, no un buffer pool heroico.

Chiste #1: Un VPS de 4GB es como un estudio — técnicamente puedes meter una caminadora, pero odiarás tu vida y también tus vecinos.

Guion de diagnóstico rápido: encuentra el cuello de botella en 10 minutos

Este es el orden en que verifico las cosas cuando “el sitio va lento” y la base de datos es la principal sospechosa. Cada paso te dice si mirar CPU, memoria, conexiones, bloqueos o almacenamiento.

Primero: ¿la máquina está hambrienta (CPU, RAM, swap)?

  • Revisa la carga vs número de CPUs.
  • Revisa actividad de swap y fallos de página mayores.
  • Revisa el historial del OOM killer.

Segundo: ¿es latencia de almacenamiento (IOPS/fsync/WAL/redo)?

  • Alto iowait, fsync lentos, tiempos de commit largos o checkpoints estancados.
  • Busca profundidad de cola y tiempos medios de espera.

Tercero: ¿presión de conexiones?

  • Demasiadas conexiones o hilos en la BD.
  • Tormentas de conexiones desde workers PHP.
  • Conteos de hilos/procesos alcanzando la memoria.

Cuarto: ¿bloqueos o transacciones largas?

  • MySQL: metadata locks, locks de InnoDB, transacciones de larga duración.
  • Postgres: consultas bloqueadas, sesiones idle-in-transaction, vacuum bloqueado por snapshots antiguos.

Quinto: ¿consultas malas + índices faltantes?

  • Los slow query logs / pg_stat_statements muestran a los mayores ofensores.
  • Busca scans de tablas completas y “filesort”/tablas temporales o scans secuenciales con grandes conteos de filas.

Eso es todo. No empieces cambiando controles al azar. No copies un “my.cnf de alto rendimiento” de un servidor de 64GB. Mide, luego elige un cambio que puedas explicar.

Cita (idea parafraseada): La idea de confiabilidad de John Allspaw: la producción es donde las suposiciones van a morir, así que diseña y opera para el aprendizaje, no para la certeza.

Tareas prácticas: comandos, salidas y qué hacer después

Estas son tareas reales que puedes ejecutar en un VPS Linux. Cada una incluye: el comando, qué significa la salida típica y la decisión que tomas. Ejecútalas en orden cuando estés triageando o estableciendo bases.

Tarea 1: Confirma la presión básica del sistema (CPU, RAM, swap)

cr0x@server:~$ uptime
 14:22:19 up 36 days,  3:18,  1 user,  load average: 5.84, 5.12, 3.90

Qué significa: En un VPS de 2 vCPU, cargas medias por encima de ~2–3 durante períodos sostenidos suelen indicar colas de procesos listos (CPU) o esperas I/O ininterrumpibles.

Decisión: Si la carga es alta, verifica inmediatamente iowait y memoria/swap antes de tocar configuraciones de la base de datos.

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           3.8Gi       3.3Gi       120Mi        90Mi       420Mi       220Mi
Swap:          1.0Gi       860Mi       164Mi

Qué significa: Uso de swap con “available” bajo es una señal roja para cargas web sensibles a latencia.

Decisión: Si el swap se usa durante el pico, reduce la huella de memoria del DB y/o reduce los workers de PHP-FPM. Considera habilitar una pequeña cantidad de swap solo como colchón contra crashes, no como capa de rendimiento.

Tarea 2: Verifica swapping activo e 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
 2  1 880000 120000  28000 380000   12   45   210   480  520  900 18  8 40 34  0
 3  0 880000 118000  28000 381000    0   60   160   510  600 1100 20  9 35 36  0
 1  2 880000 115000  28000 379000   20    0   400   200  480  850 15  7 38 40  0
 4  0 880000 119000  28000 382000    0    8    90   520  610 1200 22 10 32 36  0
 2  1 880000 117000  28000 381500    0   35   200   600  590 1050 19  9 34 38  0

Qué significa: Valores no nulos en si/so indican swapping. Alto wa indica espera por I/O.

Decisión: Si el swapping está activo, deja de tunear consultas y arregla conteos de memoria/conexiones. Si el iowait es alto, enfócate en latencia de almacenamiento, comportamiento de checkpoints/flush y en reducir la amplificación de escrituras.

Tarea 3: Identificar latencia de disco y colas rápidamente

cr0x@server:~$ iostat -xz 1 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.20    0.00    8.10   34.50    0.00   39.20

Device            r/s     w/s   rkB/s   wkB/s  rrqm/s  wrqm/s  %util  await  aqu-sz
vda              12.0    48.0   900.0  6200.0    0.2     3.1   92.0   21.5    1.8

Qué significa: %util cerca de 100% y await > 10–20ms en un disco de VPS suele significar que tu almacenamiento es el cuello de botella.

Decisión: Reduce escrituras (batching, ajuste de autovacuum, tuning de flush de InnoDB), mueve la BD a mejor almacenamiento o separa DB y web. En algunos planes de VPS, el único “tunear” que importa es pagar por más IOPS.

Tarea 4: Confirma qué BD estás ejecutando y versión

cr0x@server:~$ mysql --version
mysql  Ver 8.0.36-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))

Qué significa: Las versiones mayores cambian defaults y eliminan knobs (como query cache). La versión te dice qué consejos son aplicables.

Decisión: No apliques recetas de tuning a menos que coincidan con tu versión mayor y tu sabor (MySQL vs MariaDB).

cr0x@server:~$ psql --version
psql (PostgreSQL) 16.1 (Ubuntu 16.1-1.pgdg22.04+1)

Qué significa: Versiones más nuevas de Postgres mejoran vacuum, WAL y planner. Eso cambia “qué duele” en cajas pequeñas.

Decisión: En Postgres antiguo quizás necesites más cuidado manual. En Postgres nuevo enfócate más en pooling de conexiones y umbrales de autovacuum.

Tarea 5: Cuenta conexiones DB (MySQL)

cr0x@server:~$ mysql -e "SHOW STATUS LIKE 'Threads_connected';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 185   |
+-------------------+-------+

Qué significa: 185 conexiones en un VPS de 4GB con PHP suele ser problema, incluso antes de que las consultas se pongan lentas.

Decisión: Limita la concurrencia de la app, habilita conexiones persistentes con cautela o mueve a un patrón que limite la concurrencia DB (colas en la app, caché o separar lecturas). Si no puedes controlar la app, reduce max_connections y acepta fallos controlados en lugar de un colapso total.

Tarea 6: Cuenta conexiones DB (PostgreSQL)

cr0x@server:~$ sudo -u postgres psql -c "SELECT count(*) AS connections FROM pg_stat_activity;"
 connections
-------------
         142
(1 row)

Qué significa: 142 sesiones de Postgres equivalen a 142 procesos backend. En un VPS de 4GB, eso es un impuesto de memoria y de cambio de contexto.

Decisión: Instala pgBouncer y baja max_connections. En cajas pequeñas, Postgres sin pooling es una broma de mal gusto que te haces a ti mismo.

Tarea 7: Encuentra consultas de larga duración y bloqueos (PostgreSQL)

cr0x@server:~$ sudo -u postgres psql -c "SELECT pid, now()-query_start AS age, state, wait_event_type, wait_event, left(query,80) AS q FROM pg_stat_activity WHERE state <> 'idle' ORDER BY age DESC LIMIT 5;"
 pid  |   age    | state  | wait_event_type | wait_event |                                       q
------+----------+--------+-----------------+------------+--------------------------------------------------------------------------------
 9123 | 00:02:18 | active | Lock            | relation   | UPDATE orders SET status='paid' WHERE id=$1
 9051 | 00:01:44 | active | IO              | DataFileRead | SELECT * FROM products WHERE slug=$1
(2 rows)

Qué significa: Esperas por locks apuntan a contención; esperas por IO apuntan a almacenamiento lento o misses de caché.

Decisión: Si las esperas por Lock dominan, arregla el alcance de las transacciones y el indexado. Si las esperas por IO dominan, aumenta el caching efectivo (con cautela) y reduce lecturas aleatorias mediante índices y modelado de consultas.

Tarea 8: Encuentra esperas por locks (MySQL)

cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST;"
Id	User	Host	db	Command	Time	State	Info
210	app	10.0.0.12:50344	shop	Query	75	Waiting for table metadata lock	ALTER TABLE orders ADD COLUMN foo INT
238	app	10.0.0.15:38822	shop	Query	12	Sending data	SELECT * FROM orders WHERE created_at > NOW() - INTERVAL 1 DAY

Qué significa: Los metadata locks pueden congelar escrituras y lecturas detrás de cambios de esquema, dependiendo de la operación y la versión.

Decisión: Deja de hacer cambios de esquema en línea a la ligera en un VPS pequeño. Programa mantenimiento o usa herramientas de migración de esquema online diseñadas para reducir bloqueos.

Tarea 9: Revisa la tasa de aciertos del InnoDB buffer pool y presión de lectura

cr0x@server:~$ mysql -e "SHOW STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+---------+
| Variable_name                         | Value   |
+---------------------------------------+---------+
| Innodb_buffer_pool_read_requests      | 9823412 |
| Innodb_buffer_pool_reads              | 412390  |
+---------------------------------------+---------+

Qué significa: reads son lecturas físicas; read_requests son lógicas. Si las lecturas físicas son altas respecto a requests, estás perdiendo caché.

Decisión: Si el working set cabe en RAM, incrementa innodb_buffer_pool_size con cautela. Si no cabe, prioriza índices y reducir el working set (menos columnas, menos scans).

Tarea 10: Revisa caché de Postgres y derrames a archivos temporales

cr0x@server:~$ sudo -u postgres psql -c "SELECT datname, blks_hit, blks_read, temp_files, temp_bytes FROM pg_stat_database ORDER BY temp_bytes DESC LIMIT 5;"
  datname  | blks_hit | blks_read | temp_files |  temp_bytes
-----------+----------+-----------+------------+--------------
 appdb     |  9201123 |   612332  |      1832  | 2147483648
(1 row)

Qué significa: Mucho temp_bytes sugiere que ordenamientos/hashes están desbordando a disco porque work_mem es demasiado pequeño para esas operaciones—o las consultas están pidiendo demasiado.

Decisión: No subas work_mem globalmente en un VPS pequeño. Arregla consultas e índices primero; luego aumenta work_mem por rol o por sesión para cargas específicas.

Tarea 11: Ver las consultas top (Postgres, si pg_stat_statements está habilitado)

cr0x@server:~$ sudo -u postgres psql -c "SELECT calls, mean_exec_time, rows, left(query,80) AS q FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;"
 calls | mean_exec_time | rows |                                       q
-------+----------------+------+--------------------------------------------------------------------------------
 82021 |          12.45 |    1 | SELECT id FROM sessions WHERE token=$1
  1220 |         210.12 |  300 | SELECT * FROM orders WHERE user_id=$1 ORDER BY created_at DESC LIMIT 50
(2 rows)

Qué significa: Las consultas con alto tiempo total son las que consumen tu presupuesto. Las de alto conteo son “muerte por mil cortes”.

Decisión: Indexa las rutas calientes y reduce consultas chatty. Si una consulta se ejecuta 80k veces y tarda 12ms, es la razón de haber desperdiciado un core.

Tarea 12: Habilita y lee el slow query log de MySQL rápidamente

cr0x@server:~$ mysql -e "SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=0.5; SET GLOBAL log_queries_not_using_indexes=ON;"
...output omitted...

Qué significa: Estás activando la recolección de evidencia. Mantén umbrales razonables para no hacerte DOS con logs.

Decisión: Recoge durante 15–60 minutos en pico, luego usa los datos para arreglar los peores ofensores. Desactiva log_queries_not_using_indexes si es demasiado ruidoso para tu app.

cr0x@server:~$ sudo tail -n 5 /var/log/mysql/mysql-slow.log
# Query_time: 1.204  Lock_time: 0.000 Rows_sent: 50  Rows_examined: 84512
SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 50;

Qué significa: Rows examined es enorme respecto a rows sent: clásico índice faltante o índice en orden incorrecto.

Decisión: Añade/ajusta índices compuestos para coincidir con el patrón filter + sort (por ejemplo, (user_id, created_at)), y verifica con EXPLAIN.

Tarea 13: Revisa espacio en sistema de ficheros y presión de inodos

cr0x@server:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        80G   74G  2.1G  98% /

Qué significa: Discos casi llenos destruyen rendimiento y pueden romper escrituras de BD. Postgres puede negarse a checkpoint; MySQL puede fallar o quedarse en solo-lectura según circunstancias.

Decisión: Libera espacio ahora. Luego configura rotación de logs y una alerta de monitorización en 80–85%.

Tarea 14: Revisa memoria de kernel y procesos DB

cr0x@server:~$ ps -eo pid,comm,rss,pmem --sort=-rss | head
 2311 mysqld     1854320 46.2
 1822 php-fpm8.1  412000 10.2
 1825 php-fpm8.1  405000 10.1
  911 postgres    220000  5.4

Qué significa: RSS muestra memoria residente real. Unos pocos workers grandes de PHP más un caché grande de BD pueden empujar la máquina al swap.

Decisión: Si DB + PHP ya consumen la mayor parte de la RAM, deja de aumentar buffers del DB. Reduce la concurrencia y limita los consumidores de memoria.

Si eliges MySQL: qué configurar primero en un VPS de 4GB

MySQL en hardware de VPS pequeño suele funcionar si no lo tratas como un pozo sin fondo para conexiones y memoria. InnoDB es tu motor por defecto; ajusta para InnoDB, no por nostalgia.

1) Define innodb_buffer_pool_size como una persona adulta

Objetivo: Cachear datos/índices calientes, reducir lecturas aleatorias, evitar dejar sin memoria al resto.

  • Si la BD está en la misma máquina que el web: comienza alrededor de 1.0–1.5GB.
  • Si la BD está mayormente sola: hasta 2.0–2.5GB puede funcionar.

Modo de fallo: Sobredimensionar el buffer pool no “usa memoria libre”. Compite con la caché de página del SO y la capa web. Luego haces swap. Entonces cada consulta se vuelve un benchmark de almacenamiento.

2) Establece max_connections más bajo de lo que piensas

Los hilos de MySQL consumen memoria. A las apps PHP les encanta abrir conexiones como si fueran gratis. No lo son.

  • Empieza alrededor de 100–200 dependiendo de la app y latencia de consultas.
  • Si ves 300–800 conexiones, no tienes un “problema de rendimiento de BD”. Tienes un problema de control de concurrencia.

3) Mantén el redo log y el comportamiento de flush sensatos

En un VPS pequeño con latencia de almacenamiento incierta, los flushs agresivos pueden causar picos. Pero convertir la durabilidad en sugerencia es como actualizar tu currículum.

  • innodb_flush_log_at_trx_commit=1 para durabilidad real (por defecto).
  • Si absolutamente debes reducir presión de fsync y puedes aceptar perder hasta 1 segundo de transacciones en un crash: considera =2. Documenta esto. Ponlo en runbooks de incidentes. No finjas que es gratis.

4) Deshabilita lo que no necesitas, pero no te quedes ciego

Performance Schema es útil; también tiene coste. En un VPS tiny puedes reducir instrumentación en lugar de eliminarla por completo.

  • Si estás constantemente CPU-bound con latencia de consultas baja, considera recortar consumidores de Performance Schema.
  • Pero mantén visibilidad suficiente para detectar regresiones. Depurar sin métricas es solo escritura creativa.

5) Ajusta límites de tablas temporales con cuidado

Las apps web adoran ORDER BY y GROUP BY, a menudo con conjuntos de resultados demasiado amplios.

  • tmp_table_size y max_heap_table_size pueden reducir tablas temporales en disco, pero ponerlos demasiado altos y reventarás memoria bajo concurrencia.

Boceto de configuración inicial para MySQL (no una religión copy-paste)

Este es el espíritu para un VPS mixto web+DB de 4GB. Ajusta según las mediciones anteriores.

cr0x@server:~$ sudo cat /etc/mysql/mysql.conf.d/99-vps-tuning.cnf
[mysqld]
innodb_buffer_pool_size = 1G
innodb_buffer_pool_instances = 1
max_connections = 150
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
slow_query_log = ON
long_query_time = 0.5

Qué significa: Buffer pool más pequeño para preservar margen, conexiones limitadas, I/O directo para reducir doble caching (depende de tu sistema de ficheros y carga), y logging de consultas lentas para evidencias.

Decisión: Aplica, reinicia en una ventana tranquila y vuelve a revisar swap/iowait y slow logs. Si la latencia mejora y el swap desaparece, vas por buen camino.

Si eliges PostgreSQL: qué configurar primero en un VPS de 4GB

Postgres es excelente para sitios web, pero te obliga a prestar atención a tres cosas desde el principio: conteo de conexiones, vacuum y WAL/checkpoints. Ignorar cualquiera de ellas y tendrás ralentizaciones “aleatorias” que en realidad no lo son.

1) Instala pooling de conexiones (pgBouncer) antes de “necesitarlo”

En 4GB, los backends de Postgres no son descartables. Un pico de tráfico que abre cientos de conexiones puede convertirse en presión de memoria y sobrecarga de cambio de contexto.

Haz: ejecuta pgBouncer en modo transaction pooling para cargas web típicas.

No hagas: subir max_connections a 500 y llamarlo escalado.

2) Ajusta shared_buffers con prudencia

La regla de folklore dice “25% de la RAM”. En un VPS mixto web+DB, empezaría alrededor de:

  • 512MB a 1GB para shared_buffers.

Postgres también se beneficia de la caché de página del SO. Darle todo a shared_buffers puede dejar sin recursos al SO y a otros procesos.

3) Mantén work_mem bajo globalmente; súbelo quirúrgicamente

work_mem es por operación de sort/hash, por consulta, por backend. No tienes RAM suficiente para valentías aquí.

  • Comienza en 4–16MB globalmente dependiendo de la concurrencia.
  • Aumenta para un rol o sesión específica si tienes una consulta pesada conocida.

4) Mantén autovacuum saludable

Autovacuum no es limpieza opcional. Es cómo Postgres evita bloat de tablas e índices y mantiene posibles los index-only scans.

  • Monitorea tuples muertas y retraso de vacuum.
  • Ajusta umbrales de autovacuum por tabla caliente si es necesario.

5) Haz checkpoints menos puntuales

En almacenamiento lento de VPS, los picos de checkpoint aparecen como acantilados de latencia aleatorios. Checkpoints más suaves reducen el dolor.

  • Aumenta checkpoint_timeout (con moderación).
  • Fija checkpoint_completion_target alto para repartir escrituras.

Boceto de configuración inicial para Postgres

cr0x@server:~$ sudo cat /etc/postgresql/16/main/conf.d/99-vps-tuning.conf
shared_buffers = 768MB
effective_cache_size = 2304MB
work_mem = 8MB
maintenance_work_mem = 128MB
checkpoint_completion_target = 0.9
checkpoint_timeout = 10min
wal_compression = on
log_min_duration_statement = 500ms

Qué significa: shared_buffers conservador, hint realista de cache, work_mem modesto, checkpoints más suaves y logging de consultas lentas.

Decisión: Aplica y recarga/reinicia, luego observa crecimiento de archivos temporales y tiempos de checkpoint. Si tu disco es lento, el suavizado de checkpoints se verá como menos acantilados de latencia.

Conexiones: el asesino silencioso en máquinas pequeñas

Si ejecutas sitios web, la manera más fácil de arruinar una base de datos es dejar que la aplicación decida la concurrencia. PHP-FPM workers + “abrir una conexión DB por petición” se convierte en una manada atronadora. En 4GB no sobrevives siendo más rápido. Sobrevives siendo más tranquilo.

Cómo se ve “demasiadas conexiones”

  • CPU de la BD alta pero sin trabajo útil (cambio de contexto, contención de mutex).
  • Uso de memoria que crece con el tráfico hasta el swap.
  • Latencia que aumenta incluso para consultas simples.

Qué hacer en su lugar

  • Limita la concurrencia de la app: menos hijos PHP-FPM, o configura el process manager para evitar explosiones.
  • Usa pooling: pgBouncer para Postgres; para MySQL considera pooling a nivel de aplicación o asegúrate de que las conexiones persistentes estén configuradas con sentido.
  • Falla rápido: a veces reducir max_connections es lo correcto porque protege la máquina de un thrash total.

Chiste #2: Conexiones ilimitadas es como un buffet ilimitado de camarones—suena genial hasta que te das cuenta que eres quien cierra el restaurante.

Almacenamiento y realidades del sistema de ficheros: IOPS, fsync y por qué “SSD rápido” miente

En plataformas VPS, “almacenamiento SSD” puede significar cualquier cosa, desde NVMe respetable hasta un dispositivo de bloque en red compartido teniendo un mal día. A las bases de datos les importa más la latencia que el throughput. Unos pocos milisegundos extra por fsync por commit se hacen visibles en el sitio.

Cómo las escrituras te lastiman de forma distinta en MySQL vs PostgreSQL

  • MySQL/InnoDB: redo logging + doublewrite buffer (según configuración/version) + flushing de páginas sucias. Flushs en ráfaga pueden amplificar la latencia.
  • PostgreSQL: WAL writes + checkpoints + background writer. Vacuum también genera I/O, y el bloat aumenta el I/O futuro.

Mejor práctica en VPS pequeño: reduce primero la amplificación de escrituras

  • Arregla apps chatty (demasiadas transacciones pequeñas).
  • Batch escrituras donde la consistencia lo permita.
  • Evita actualizar constantemente columnas “last_seen” en cada petición si no lo necesitas.
  • Mantén índices ligeros; cada índice es un impuesto de escritura.

Trampas del sistema de ficheros

  • No pongas bases de datos en sistemas de ficheros de red inestables a menos que conozcas las garantías de durabilidad de la plataforma.
  • Atento a condiciones de disco lleno: Postgres y MySQL se comportan mal de diferentes formas, pero ninguna es “agradable”.

Tres mini-historias corporativas desde las trincheras

1) El incidente causado por una suposición errónea: “La caché lo cubrirá”

Un equipo pequeño ejecutaba varios sitios de marketing y un servicio de checkout en un único VPS de 4GB. Tenía MySQL, Nginx y PHP-FPM. El tráfico era “principalmente estático”, lo que era cierto hasta que lanzaron una campaña y el servicio de checkout empezó a recibir ráfagas de peticiones autenticadas.

La suposición fue que la caché de páginas y la caché de la app cubrirían las lecturas, así que subieron innodb_buffer_pool_size cerca de 3GB para “hacer la base de datos rápida”. Se veía bien en una hora tranquila. Luego llegó la campaña.

PHP-FPM lanzó más procesos para manejar el tráfico. Cada worker usó más memoria de la que recuerdaba nadie. El SO empezó a swapear. El buffer pool de la base de datos era enorme, así que el kernel tenía menos espacio para todo lo demás. La latencia no aumentó gradualmente; se desplomó. El endpoint de checkout empezó a agotar tiempos, los reintentos aumentaron el tráfico, y la tormenta de reintentos convirtió un problema de recursos en un denial-of-service auto-hospedado.

La solución no fue exótica. Redujeron el buffer pool para dejar margen, limitaron PHP-FPM children, bajaron max_connections de MySQL para que el sistema fallara rápido en lugar de hacer thrash, y pusieron una cola explícita delante del checkout. También aprendieron la diferencia operacional entre “memoria libre” y “memoria disponible bajo ráfaga”.

2) La optimización que salió mal: “Solo sube work_mem, está bien”

Una app interna corría en PostgreSQL. Los usuarios se quejaban de reportes lentos, así que alguien aumentó significativamente work_mem porque un post decía que reduciría I/O a archivos temporales. Lo hizo. Para un usuario. En una sesión.

Luego vino un lunes. Varios usuarios ejecutaron reportes concurrentes. Esos reportes hicieron múltiples sorts y hash joins. Postgres asignó correctamente work_mem por operación. El uso de memoria se disparó. El VPS no murió de inmediato; se volvió cada vez más lento a medida que el swap entraba en acción. La BD parecía “viva” pero cada consulta esperaba detrás de la tormenta de I/O causada por el swapping.

El equipo revertió work_mem a un valor conservador y en su lugar arregló la consulta del reporte. Añadieron un índice faltante, redujeron columnas seleccionadas y crearon una tabla resumen que se actualizaba periódicamente. Para la consulta realmente pesada usaron un rol con mayor work_mem y la ejecutaron por una ruta de reporting controlada. La lección no fue “nunca tunear”. Fue “no tunear globalmente por un problema local en una máquina pequeña”.

3) La práctica aburrida pero correcta que salvó el día: “Limita conexiones y registra consultas lentas”

Una organización diferente alojaba varios sitios pequeños de clientes en un VPS compartido de 4GB. Nada sofisticado. No buscaban microsegundos. Hicieron tres cosas aburridas desde el día uno: limitaron conexiones de BD, activaron slow query logging con un umbral sensato y monitorizaron el uso de disco con una alerta mucho antes del 90%.

Una tarde una actualización de plugin introdujo una regresión en una consulta. El sitio no se cayó de inmediato porque los límites de conexión impidieron que la carga ilimitada se amontonara en la BD. En su lugar, algunas peticiones fallaron rápido, lo que hizo visible el problema sin fundir la máquina.

El slow query log tenía la evidencia: una consulta que empezó a escanear una tabla grande sin índice útil. Añadieron el índice, resolvieron la regresión y el incidente quedó confinado a una ventana corta. Sin misterio. Sin “se arregló solo”. Sin arqueología de fin de semana.

Esto es lo que parece la fiabilidad aburrida: fallos controlados, recolección de evidencia y suficiente margen para que un deploy malo no se convierta en catástrofe del sistema.

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

1) Síntoma: paradas repentinas de 10–60s en todo el sitio

Causa raíz: picos de latencia de almacenamiento durante checkpoints/flushes o tormentas de swap.

Solución: confirma con iostat y vmstat; reduce presión de memoria (buffers DB más pequeños, menos app workers), suaviza checkpoints (Postgres) y reduce amplificación de escrituras (ambos).

2) Síntoma: CPU de la base de datos alta, consultas “no tan lentas” individualmente

Causa raíz: demasiadas conexiones concurrentes; la sobrecarga de contención domina.

Solución: limita conexiones; añade pooling (pgBouncer); reduce concurrencia de PHP-FPM; cachea en la app o en el reverse proxy; falla rápido en lugar de thrash.

3) Síntoma: Postgres crece y crece; el rendimiento se degrada lentamente

Causa raíz: retraso de vacuum y bloat de tablas/índices por autovacuum insuficiente o transacciones largas.

Solución: identifica sesiones idle-in-transaction, ajusta autovacuum por tabla caliente y deja de mantener transacciones abiertas entre peticiones.

4) Síntoma: MySQL “Waiting for table metadata lock” en processlist

Causa raíz: cambio de esquema o DDL bloqueado por transacciones largas; consultas se encolan detrás de metadata locks.

Solución: programa DDL en ventanas de mantenimiento; mantén transacciones cortas; usa enfoques de cambio de esquema online si es necesario.

5) Síntoma: muchas archivos temporales o “Using temporary; Using filesort” en MySQL

Causa raíz: índices faltantes para patrones ORDER BY/GROUP BY; consultas ordenando datasets enormes.

Solución: añade índices compuestos que coincidan con filter+sort; reduce columnas seleccionadas; pagina correctamente; evita paginación con OFFSET para páginas profundas.

6) Síntoma: errores frecuentes de “too many connections”

Causa raíz: fugas de conexiones en la app, sin pooling o picos en conteo de workers web.

Solución: pool de conexiones; establece timeouts sensatos; limita concurrencia de la app; fija max_connections a un número que puedas permitirte.

7) Síntoma: después de “tunear”, el rendimiento empeoró

Causa raíz: un ajuste global (como work_mem o un buffer pool demasiado grande) aumentó la memoria por conexión y disparó el swap bajo concurrencia.

Solución: revierte; aplica tuning por usuario/consulta; mide memoria y concurrencia explícitamente.

Listas de verificación / plan paso a paso

Paso 0: Decide qué significa “bueno”

  • Elige un objetivo estilo SLO: por ejemplo, homepage p95 < 500ms, checkout p95 < 800ms.
  • Elige una ventana de medición y captura la línea base (CPU, RAM, swap, iowait, conexiones DB, consultas lentas).

Paso 1: Estabiliza el host

  • Asegura que el disco tenga al menos 15–20% de espacio libre.
  • Asegura que no estás swappeando bajo tráfico pico normal.
  • Configura límites de servicio conservadores (systemd limits si es necesario) para evitar procesos desbocados.

Paso 2: Limita la concurrencia deliberadamente

  • Fija PHP-FPM max children a un número que puedas permitir en RAM.
  • Fija max_connections de la BD para proteger la máquina.
  • En Postgres: despliega pgBouncer y reduce conexiones backend.

Paso 3: Ajusta los primeros knobs de memoria

  • MySQL: ajusta innodb_buffer_pool_size para que el working set quepa sin dejar sin recursos al SO.
  • Postgres: ajusta shared_buffers con prudencia; mantiene work_mem bajo globalmente.

Paso 4: Activa la recolección de evidencia

  • MySQL: slow query log a 0.5–1s durante pico, luego analiza y arregla.
  • Postgres: log_min_duration_statement y idealmente pg_stat_statements.

Paso 5: Arregla los 3 patrones de consulta más importantes

  • Añade los índices faltantes que reduzcan scans de filas.
  • Elimina N+1 queries en la app.
  • Deja de hacer consultas caras por petición; precomputa o cachea.

Paso 6: Re-prueba y establece guardarraíles

  • Vuelve a ejecutar tus tareas de triage en pico.
  • Añade alertas sobre actividad de swap, utilización de disco, conteos de conexiones y tasa de consultas lentas.
  • Documenta tus ajustes “seguros” y la razón para que el tú del futuro no los revierta.

Preguntas frecuentes

1) En un VPS de 4GB, ¿debo priorizar caché de BD o caché de página del SO?

Prioriza estabilidad. Para un single-box web+DB, no dejes al SO sin memoria. Una caché moderada en la BD más margen supera a una caché gigantesca que provoca swap en ráfagas.

2) ¿Es PostgreSQL “más lento” que MySQL para sitios web?

No en general. Para muchas cargas web, cualquiera es suficientemente rápido si está bien indexado. El mayor diferenciador en 4GB es la gestión de conexiones y patrones de escritura, no la velocidad bruta del motor.

3) ¿Cuál es el primer ajuste de MySQL que debería cambiar?

innodb_buffer_pool_size, dimensionado a tu realidad. Luego limita max_connections. Luego habilita slow query logging y arregla lo que muestre.

4) ¿Cuál es el primer ajuste de PostgreSQL que debería cambiar?

Estrategia de pooling de conexiones (pgBouncer) y max_connections. Luego shared_buffers conservador y logging/pg_stat_statements para identificar las consultas principales.

5) ¿Puedo simplemente aumentar swap para resolver problemas de memoria?

Puedes aumentar swap para evitar crashes abruptos por OOM, pero swap no es RAM de rendimiento. Si tu base de datos o workers PHP golpean regularmente el swap, la latencia será impredecible.

6) ¿Debería desactivar fsync para ganar velocidad?

No en producción si te importa la integridad de datos. Si desactivas durabilidad y el host cae, puedes perder datos. A los benchmarks les encanta; a los clientes no.

7) ¿Cómo sé si estoy limitado por I/O?

Alto iowait en vmstat, alto await y %util en iostat, y sesiones DB esperando en eventos de IO (Postgres) son señales fuertes.

8) ¿Cuándo debería separar web y BD en servidores distintos?

Cuando tus cambios de tuning se convierten en compensaciones entre la capa web y la BD, o cuando la latencia de almacenamiento hace impredecibles las escrituras de BD. Separar te da aislamiento y planificación de capacidad más clara.

9) ¿Los valores por defecto son suficientes hoy en día?

Los defaults son mejores que antes, pero no están adaptados a tu situación de 4GB “todo en una caja”. Los topes de conexiones y la presupuestación de memoria siguen siendo tu responsabilidad.

10) ¿Cuál es la mejora de rendimiento más segura que puedo hacer sin mucha experiencia en BD?

Habilita slow query logging (o pg_stat_statements), identifica los 3 consumidores de tiempo principales y añade los índices correctos. También limita conexiones para que el servidor permanezca estable bajo carga.

Siguientes pasos que no te avergonzarán después

En un VPS de 4GB no estás optimizando una base de datos. Estás gestionando la contención entre web, base de datos y almacenamiento tratando de mantener la latencia aburrida.

  1. Ejecuta el guion de diagnóstico rápido durante pico y anota qué está pasando realmente: swap, iowait, conexiones, locks, consultas top.
  2. Limita la concurrencia primero: workers PHP-FPM y conexiones DB. Añade pgBouncer si usas Postgres.
  3. Fija el primer knob de memoria (InnoDB buffer pool o shared_buffers de Postgres) a un valor conservador que deje margen.
  4. Activa evidencia (slow query logs / pg_stat_statements) y arregla los mayores offenders con índices y cambios de consulta.
  5. Revisa disco y comportamiento de escrituras; suaviza checkpoints, reduce derrames a temp y deja de hacer escrituras ruidosas innecesarias.
  6. Decide si la solución real es arquitectónica: mover la BD a un VPS separado, mejorar el tier de almacenamiento o usar una BD gestionada. A veces el parámetro de tuning más efectivo es tu factura.

Si haces solo una cosa hoy: limita conexiones y deja de swapear. Todo lo demás es adorno.

MySQL vs ClickHouse: Evita que la analítica mate al OLTP (El plan de separación limpia)

En algún lugar de tu empresa, un analista bienintencionado acaba de actualizar un panel. Ahora el checkout va lento, la API agota el tiempo de espera y el canal on-call se ha convertido en una sesión grupal de terapia.

Esto no es un problema de “consulta mala”. Es un problema de arquitectura: OLTP y analítica son animales distintos, y ponerlos en la misma jaula termina de forma predecible. La solución es una separación limpia: MySQL hace transacciones, ClickHouse hace analítica, y dejas de permitir que la curiosidad haga DDoS a tu ruta de ingresos.

El problema real: OLTP y analítica chocan en la capa de almacenamiento

OLTP trata sobre latencia, exactitud y concurrencia predecible. Se optimiza para miles de lecturas/escrituras pequeñas por segundo, índices ajustados y conjuntos de trabajo calientes que caben en memoria. El costo de una sola petición lenta se paga de inmediato—en la experiencia del cliente, en timeouts y en reintentos que amplifican la carga.

La analítica trata sobre rendimiento, escaneos amplios y agregación. Se optimiza para leer grandes volúmenes de datos, comprimirlos bien y usar ejecución vectorizada para convertir CPU en respuestas. Las consultas analíticas a menudo son “embarazosa y naturalmente paralelas” y no les importa tardar algunos segundos—hasta que apuntan a tu base transaccional y se convierten en una denegación de servicio con una tabla dinámica adjunta.

La conclusión: OLTP y analítica compiten por los mismos recursos finitos—ciclos de CPU, I/O de disco, caché de páginas, buffer pools, locks/latches y mantenimiento de fondo (flushes, checkpoints, merges). Incluso si añades una réplica de lectura, frecuentemente compartes el mismo dolor fundamental: lag de replicación, saturación de I/O y rendimiento inconsistente causado por escaneos impredecibles.

Dónde duele: contención de recursos en MySQL

  • Contaminación del buffer pool: Una gran consulta de reporte lee un segmento frío de historial, expulsa páginas calientes y de repente tu carga primaria queda limitada por disco.
  • Presión de fondo de InnoDB: Escaneos largos + tablas temporales + ordenamientos pueden incrementar páginas sucias y la presión de redo. Las tormentas de flush no son amables.
  • Bloqueos y metadata locks: Algunos patrones de reporte desencadenan interacciones feas (piensa en “ALTER TABLE durante horas de negocio” encontrándose con un “SELECT …” que mantiene MDL).
  • Lag de replicación: Lecturas pesadas en una réplica roban I/O y CPU del hilo SQL que aplica cambios.

Dónde encaja ClickHouse

ClickHouse está diseñado para analítica: almacenamiento columnar, compresión, ejecución vectorizada y paralelismo agresivo. Espera que leas muchas filas, pero solo unas pocas columnas, y te recompensa por agrupar trabajo en particiones y claves ordenadas.

La disciplina es simple: trata MySQL como el sistema de registro para transacciones. Trata ClickHouse como el sistema de verdad para analítica—“verdad” en el sentido de “derivado del registro, reproducible y consultable a escala”.

Idea parafraseada de Werner Vogels: “Todo falla; diseña para la falla.” También aplica a los datos: diseña para modos de fallo como tormentas de consultas, lag y backfills.

MySQL vs ClickHouse: las diferencias reales que importan en producción

Disposición del almacenamiento: fila vs columna

MySQL/InnoDB es orientado a filas. Excelente para obtener una fila por clave primaria, actualizar un par de columnas, mantener índices secundarios y aplicar restricciones. Pero escanear mil millones de filas para calcular agregados significa arrastrar filas completas por el motor, tocar páginas que no necesitabas y consumir caché.

ClickHouse es orientado a columnas. Lee solo las columnas que pides, las comprime bien (a menudo de forma dramática) y las procesa en vectores. Pagas por adelantado con restricciones de modelado distintas—desnormalización, claves de ordenación cuidadosas y un proceso de merge que debes respetar.

Modelo de concurrencia: transaccional vs paralelismo analítico

MySQL maneja muchas transacciones cortas concurrentes bien—hasta los límites de tu esquema, índices y hardware. ClickHouse también maneja muchas lecturas concurrentes, pero la magia está en paralelizar lecturas grandes y agregaciones eficientemente. Si apuntas una herramienta BI a ClickHouse y permites concurrencia ilimitada sin límites de consulta, intentará prender fuego a tu CPU. Puedes y debes gobernarlo.

Consistencia y corrección

MySQL es ACID (con las advertencias habituales, pero sí, es tu ancla transaccional). ClickHouse es típicamente eventualmente consistente para datos ingeridos y orientado a append. Puedes modelar updates/deletes, pero lo haces en los términos de ClickHouse (ReplacingMergeTree, CollapsingMergeTree, columnas de versión, o deletes asíncronos). Eso está bien: la analítica suele querer la verdad actual y una serie temporal de cambios, no la semántica transaccional por fila.

Indexación y patrones de consulta

Los índices de MySQL son B-trees que soportan búsquedas por punto y escaneos por rango. ClickHouse usa ordenación por clave primaria e índices dispersos, además de índices de salto de datos (como bloom filters) donde ayuda. La mejor consulta en ClickHouse es la que puede saltarse grandes trozos de datos porque tu particionado y ordenamiento coinciden con los patrones de acceso.

Postura operativa

La operación de MySQL gira en torno a la salud de la replicación, backups, migraciones de esquema y estabilidad de consultas. La operación de ClickHouse gira en torno a merges, utilización de disco, recuento de partes, TTL y gobernanza de consultas. En otras palabras: cambias un conjunto de dragones por otro. La ventaja sigue siendo válida porque dejas de permitir que la analítica arruine tu flujo de checkout.

Broma #1: Un refresco de panel es el único tipo de “participación de usuario” que puede aumentar las tasas de error y la pérdida de clientes al mismo tiempo.

Hechos y contexto histórico (útil, no trivia)

  1. InnoDB de MySQL se volvió predeterminado en MySQL 5.5 (era 2010), afianzando el comportamiento de almacén por filas para la mayoría de despliegues.
  2. ClickHouse comenzó en Yandex para soportar cargas analíticas a escala; creció en un mundo donde escanear grandes datos rápidamente era el trabajo completo.
  3. Los almacenes columnar se popularizaron porque la CPU se volvió más rápida que los discos, y compresión + ejecución vectorizada te permiten gastar CPU para evitar I/O.
  4. La “contaminación” del buffer pool de InnoDB es un modo clásico de fallo cuando escaneos largos expulsan páginas calientes; el motor no está “roto”, está haciendo lo que pediste.
  5. La analítica basada en replicación existe desde hace décadas: la gente ha estado enviando cambios OLTP a data warehouses desde antes de que “data lake” fuera una palabra de moda.
  6. El query cache de MySQL fue removido en MySQL 8.0 porque causaba contención y no escalaba bien; caché no es gratis, y los locks globales son caros.
  7. La familia MergeTree de ClickHouse almacena datos en partes y las combina en segundo plano—excelente para escrituras y compresión, pero crea señales operativas (recuento de partes, backlog de merges) que debes monitorear.
  8. El esquema estrella y el modelado dimensional preceden a las herramientas modernas; ClickHouse a menudo empuja a los equipos de vuelta hacia formas desnormalizadas y amigables para consultas porque los joins a escala tienen costos reales.

El plan de separación limpia: patrones que no derriten producción

Principio 1: MySQL es para servir usuarios, no curiosidad

Hazlo política: MySQL de producción no es una base de datos de reporting. No “usualmente.” No “excepto por una consulta rápida.” Nunca. Si alguien necesita un one-off, que lo ejecute contra ClickHouse o un entorno de snapshot controlado.

Recibirás resistencias. Es normal. El truco es reemplazar el “no” con “aquí está la forma segura.” Proporciona el camino seguro: acceso a ClickHouse, conjuntos de datos curados y un flujo de trabajo que no implique rogar al on-call por permiso para ejecutar un JOIN sobre un año de pedidos.

Principio 2: Elige una estrategia de movimiento de datos que coincida con tu tolerancia a fallos

Hay tres formas comunes de alimentar ClickHouse desde MySQL. Cada una tiene bordes afilados.

Opción A: ETL por lotes (dump y carga)

Extraes snapshots cada hora/diarios (mysqldump, exportaciones CSV, jobs de Spark), cargas en ClickHouse y aceptas la obsolescencia. Es lo más sencillo operacionalmente pero puede ser doloroso cuando necesitas métricas casi en tiempo real, y los backfills pueden ser pesados.

Opción B: Ingestión basada en replicación (CDC)

Capturas cambios del binlog de MySQL y los streamas a ClickHouse. Esto te da analítica casi en tiempo real mientras mantienes MySQL aislado de la carga de consulta. Pero introduce la salud del pipeline como una preocupación de producción de primera clase: lag, drift de esquema y re-procesamiento se vuelven tu nuevo hobby.

Opción C: Escritura dual (la aplicación escribe en ambos)

No lo hagas. O, si absolutamente debes, hazlo solo con idempotencia robusta, entrega asíncrona y un job de reconciliación que asuma que la escritura dual te mentirá ocasionalmente.

El plan de separación limpia suele significar CDC más modelos de datos curados en ClickHouse. ETL por lotes es aceptable cuando puedes tolerar obsolescencia. La escritura dual es una trampa a menos que disfrutes explicar desajustes de datos en los postmortems de incidentes.

Principio 3: Modela ClickHouse para tus preguntas, no para tu esquema

La mayoría de esquemas OLTP están normalizados. La analítica quiere menos joins, claves estables y tablas de tipo evento. Tu trabajo es construir una representación analítica que sea fácil de consultar y difícil de usar mal.

  • Prefiere tablas de eventos: orders_events, sessions, payments, shipments, support_tickets. Registra eventos append-only. Deriva métricas.
  • Particiona por tiempo: normalmente por día o mes. Esto te da pruning predecible y TTL manejable.
  • Ordena por dimensiones de consulta: coloca las claves de filtro/agrupación más comunes al inicio de ORDER BY (después de la clave de tiempo si siempre filtras por tiempo).
  • Pre-agrega donde sea estable: las materialized views pueden producir rollups para que los paneles no escaneen repetidamente datos crudos.

Principio 4: Gobernanza vence a las heroicas

ClickHouse puede responder preguntas lo suficientemente rápido como para que la gente haga preguntas peores más frecuentemente. Necesitas barandillas:

  • Separa usuarios y cuotas: los usuarios de BI obtienen timeouts y memoria máxima. ETL obtiene un perfil diferente.
  • Establece max threads y concurrencia: evita una “manada de cieno” de consultas paralelas.
  • Usa datasets “gold” dedicados: vistas o tablas estables de las que dependen los dashboards, versionadas si es necesario.
  • Define SLOs: el SLO de latencia de MySQL es sagrado. El SLO de frescura de ClickHouse es negociable pero medible.

Tareas prácticas (comandos, salidas, decisiones)

Estos son los movimientos que realmente haces a las 02:13. Cada tarea incluye un comando, salida de ejemplo, qué significa y la decisión que obtienes de ello.

Task 1: Confirmar que MySQL sufre escaneos analíticos (top digests)

cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT/1e12 AS total_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN ? AND ? GROUP BY customer_id
COUNT_STAR: 9421
total_s: 18873.214
*************************** 2. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE created_at > ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 110233
total_s: 8211.532

Qué significa: Tu mayor tiempo viene de un agregado clásico de reporting sobre un rango de fechas. No es “una consulta lenta”, es dolor repetido.

Decisión: Bloquea o redirige el patrón de consulta analítica. No conviertas MySQL en un motor OLAP. Empieza moviendo ese panel a ClickHouse o a una tabla de rollup.

Task 2: Comprobar actividad actual de threads en MySQL (¿es un dogpile?)

cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head
Id	User	Host	db	Command	Time	State	Info
31	app	10.0.2.14:51234	prod	Query	2	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
44	app	10.0.2.14:51239	prod	Query	2	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id
57	app	10.0.2.14:51241	prod	Query	1	Sending data	SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id

Qué significa: Muchas consultas idénticas se están ejecutando concurrentemente. Es un panel o una flota de workers haciendo el mismo trabajo caro.

Decisión: Limita en la capa de app/BI e introduce caching o pre-aggregación en ClickHouse. Considera también límites de conexión de MySQL y controles de recursos por usuario.

Task 3: Validar presión en el buffer pool de InnoDB (páginas calientes expulsadas)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
Variable_name	Value
Innodb_buffer_pool_read_requests	987654321
Innodb_buffer_pool_reads	12345678

Qué significa: Un número alto de lecturas físicas (Innodb_buffer_pool_reads) relativo a lecturas lógicas sugiere que tu conjunto de trabajo no se mantiene en memoria—a menudo debido a escaneos grandes.

Decisión: Detén los escaneos (mueve la analítica fuera) y solo entonces considera aumentar el buffer pool o ajustar la carga. El hardware no puede sobrepasar una mezcla de carga mala para siempre.

Task 4: Detectar saturación de I/O de disco en el host MySQL

cr0x@server:~$ iostat -xz 1 3
Linux 6.2.0 (mysql01) 	12/30/2025 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.12    0.00    6.44   31.55    0.00   43.89

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await aqu-sz  %util
nvme0n1         820.0  64200.0     0.0    0.0   12.4    78.3     410.0  18800.0    9.8   18.2   98.7

Qué significa: %util cercano a 100% y alto iowait significa que el disco es el cuello de botella. A los escaneos analíticos les encanta este resultado.

Decisión: Inmediato: reduce la concurrencia de consultas, mata los peores culpables, cambia la analítica a ClickHouse. A largo plazo: separa almacenamiento y cargas; no confíes en “NVMe más rápido” como estrategia.

Task 5: Identificar lag de replicación en MySQL (tu “réplica de lectura” no está ayudando)

cr0x@server:~$ mysql -h mysql-replica01 -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_SQL_Running|Slave_IO_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 487

Qué significa: La réplica está ~8 minutos detrás. Los paneles que la leen están mintiendo. Peor: si haces failover, podrías perder transacciones recientes.

Decisión: No uses la réplica como sumidero analítico. Usa CDC hacia ClickHouse, o al menos una réplica dedicada con acceso de consulta controlado y recursos garantizados.

Task 6: Mostrar el plan de la consulta cara (deja de adivinar)

cr0x@server:~$ mysql -e "EXPLAIN SELECT customer_id, sum(total) FROM orders WHERE created_at BETWEEN '2025-12-01' AND '2025-12-30' GROUP BY customer_id\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: orders
type: range
possible_keys: idx_created_at
key: idx_created_at
rows: 98234123
Extra: Using where; Using temporary; Using filesort

Qué significa: Incluso con un índice, estás escaneando ~98M filas y usando temp/filesort. Eso no es una consulta OLTP; es un trabajo OLAP.

Decisión: Muévela. Si debes mantener algunos agregados en MySQL, usa tablas resumen actualizadas incrementalmente, no GROUP BY ad hoc sobre hechos crudos.

Task 7: Confirmar lo básico de salud de ClickHouse (¿son merges o disco el problema?)

cr0x@server:~$ clickhouse-client -q "SELECT hostName(), uptime()"
ch01
345678

Qué significa: Puedes conectar y el servidor ha estado vivo suficiente tiempo para ser útil.

Decisión: Procede a verificaciones más profundas: partes/merges, carga de consultas y disco.

Task 8: Comprobar consultas activas en ClickHouse y su uso de recursos

cr0x@server:~$ clickhouse-client -q "SELECT user, query_id, elapsed, read_rows, formatReadableSize(memory_usage) AS mem, left(query, 80) AS q FROM system.processes ORDER BY memory_usage DESC LIMIT 5 FORMAT TabSeparated"
bi_user	0f2a...	12.4	184001234	6.31 GiB	SELECT customer_id, sum(total) FROM orders_events WHERE event_date >= toDate('2025-12-01')
etl	9b10...	3.1	0	512.00 MiB	INSERT INTO orders_events FORMAT JSONEachRow

Qué significa: BI está consumiendo memoria. Está bien si está presupuestado. Es un problema si roba merges o provoca OOM.

Decisión: Establece max_memory_usage por usuario, max_threads y posiblemente max_concurrent_queries. Mantén ETL fiable.

Task 9: Ver backlog de merges en ClickHouse (partes creciendo como maleza)

cr0x@server:~$ clickhouse-client -q "SELECT database, table, sum(parts) AS parts, formatReadableSize(sum(bytes_on_disk)) AS disk FROM system.parts WHERE active GROUP BY database, table ORDER BY sum(parts) DESC LIMIT 10 FORMAT TabSeparated"
analytics	orders_events	1842	1.27 TiB
analytics	sessions	936	640.12 GiB

Qué significa: Miles de partes pueden significar fragmentación de inserts o merges quedando atrás. El rendimiento de consultas se degradará, y el arranque/metadata se vuelve más pesado.

Decisión: Ajusta el batching de inserts, afina configuraciones de merge con cautela y considera la estrategia de particionado. Si las partes siguen subiendo, trátalo como un incidente en cámara lenta.

Task 10: Validar pruning de particiones (si escanea todo, lo modelaste mal)

cr0x@server:~$ clickhouse-client -q "EXPLAIN indexes=1 SELECT customer_id, sum(total) FROM analytics.orders_events WHERE event_date BETWEEN toDate('2025-12-01') AND toDate('2025-12-30') GROUP BY customer_id"
Expression ((Projection + Before ORDER BY))
  Aggregating
    Filter (WHERE)
      ReadFromMergeTree (analytics.orders_events)
        Indexes:
          MinMax
            Keys: event_date
            Condition: (event_date in [2025-12-01, 2025-12-30])
            Parts: 30/365
            Granules: 8123/104220

Qué significa: Está leyendo 30/365 partes gracias al filtro de fecha. Eso es lo que “funciona según el diseño” parece.

Decisión: Si las partes leídas se acercan al total, cambia el particionado y/o exige filtros de tiempo en los dashboards.

Task 11: Monitorizar uso de disco en ClickHouse y predecir problemas de capacidad

cr0x@server:~$ df -h /var/lib/clickhouse
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme1n1    3.5T  3.1T  330G  91% /var/lib/clickhouse

Qué significa: 91% usado. Estás a un backfill de un mal día, y los merges necesitan espacio libre.

Decisión: Detén backfills no esenciales, amplía almacenamiento, aplica TTL y comprime/optimiza el modelo de datos. ClickHouse bajo presión de disco se vuelve impredeciblemente lento y arriesgado.

Task 12: Verificar lag del pipeline CDC en el consumidor (¿la analítica está obsoleta?)

cr0x@server:~$ clickhouse-client -q "SELECT max(ingested_at) AS last_ingest, now() AS now, dateDiff('second', max(ingested_at), now()) AS lag_s FROM analytics.orders_events"
2025-12-30 19:03:12	2025-12-30 19:03:29	17

Qué significa: ~17 segundos de lag. Eso es saludable para analítica “casi en tiempo real”.

Decisión: Si el lag sube, pausa consultas pesadas, revisa el throughput del pipeline y decide si degradar dashboards o arriesgar OLTP.

Task 13: Comprobar formato de binlog de MySQL para corrección de CDC

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'binlog_format';"
Variable_name	Value
binlog_format	ROW

Qué significa: ROW suele ser lo que las herramientas CDC quieren para corrección. STATEMENT puede ser ambiguo para queries no deterministas.

Decisión: Si no estás en ROW, planifica una ventana de cambios. La corrección de CDC no es algo por lo que debas “tener esperanza”.

Task 14: Confirmar que MySQL tiene logging de consultas lentas sensato (para probar causalidad)

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
Variable_name	Value
slow_query_log	ON
slow_query_log_file	/var/log/mysql/mysql-slow.log
Variable_name	Value
long_query_time	0.500000

Qué significa: Capturarás consultas más lentas que 500ms. Es agresivo, pero útil durante un periodo ruidoso.

Decisión: Durante incidentes, baja long_query_time temporalmente y muestrea. Después, ponlo a un umbral estable y usa resúmenes por digest.

Task 15: Verificar límites de usuario en ClickHouse (evitar una “fiesta de paralelismo” BI)

cr0x@server:~$ clickhouse-client -q "SHOW CREATE USER bi_user"
CREATE USER bi_user IDENTIFIED WITH sha256_password SETTINGS max_memory_usage = 4000000000, max_threads = 8, max_execution_time = 60, max_concurrent_queries = 5

Qué significa: BI está acotado: 4GB de memoria, 8 threads, 60s de runtime, 5 consultas concurrentes. Esa es la diferencia entre un dashboard y una prueba de estrés.

Decisión: Si no puedes poner límites porque “negocio lo necesita”, no estás corriendo analítica, estás jugando a la ruleta.

Guía de diagnóstico rápido

Este es el orden que encuentra el cuello de botella rápido, sin convertir el incidente en un debate filosófico.

Primero: ¿MySQL está sobrecargado por lecturas, escrituras, locks o I/O?

  1. Top query digests (performance_schema digests o resumen del slow log): identifica las familias de consultas que consumen tiempo.
  2. Estados de threads (SHOW PROCESSLIST): “Sending data” sugiere scan/aggregación; “Locked” sugiere contención; “Waiting for table metadata lock” sugiere colisión de DDL.
  3. I/O de disco (iostat): si iowait es alto y %util del disco está al máximo, detén los escaneos antes de ajustar cualquier otra cosa.

Segundo: ¿la “solución” (réplica) realmente lo empeora?

  1. Lag de replicación (SHOW SLAVE STATUS): si el lag es de minutos, los usuarios analíticos están tomando decisiones sobre datos obsoletos y te culpan por ello.
  2. Contención en la réplica: consultas pesadas pueden dejar sin recursos al hilo SQL y aumentar aún más el lag.

Tercero: Si ClickHouse existe, ¿está sano y gobernado?

  1. system.processes: identifica consultas BI desbocadas y consumidores de memoria.
  2. Parts y merges (system.parts): demasiadas parts significa problemas de forma de ingestión o backlog de merges.
  3. Espacio en disco (df): merges y TTL necesitan espacio; 90% lleno es deuda operativa con intereses.

Cuarto: ¿la frescura de los datos es la queja real?

  1. Lag de CDC (max ingested_at): cuantifica la obsolescencia.
  2. Comunica una alternativa: si la frescura se degrada, degrada los dashboards—no el checkout.

Tres mini-historias corporativas desde la trinchera

Incidente causado por una suposición errónea: “Las réplicas de lectura son para reporting”

Una empresa de suscripción mediana tenía un cluster MySQL primario y dos réplicas de lectura. Su herramienta BI apuntaba a una réplica porque “las lecturas no afectan las escrituras”. Esa frase ha causado más incidentes que la cafeína ha prevenido.

Durante cierre de mes, finanzas ejecutó un conjunto de informes de cohortes y de ingresos. El disco de la réplica alcanzó saturación: escaneos pesados más tablas temporales. El lag de replicación subió de segundos a decenas de minutos. Nadie lo notó al principio porque el tráfico de la aplicación estaba bien; el primario no se vio afectado directamente.

Entonces alguien hizo la segunda suposición: “Si el primario falla, podemos hacer failover a una réplica.” Justo cuando el lag era peor, el primario tuvo un problema de host no relacionado y entró en estado no saludable. La automatización intentó promover la “mejor” réplica—excepto que la “mejor” tenía 20 minutos de retraso.

No perdieron toda la base de datos. Perdieron suficientes transacciones recientes como para crear una pesadilla en soporte al cliente: pagos que “sucedieron” externamente pero no existían internamente, y sesiones que no coincidían con facturación. La recuperación fue una mezcla cuidadosa de rastreo de binlogs y reconciliación contra el proveedor de pagos.

La solución no fue heroica. Separaron responsabilidades: una réplica dedicada para failover con bloqueo estricto de consultas, y la analítica se movió a ClickHouse vía CDC. El reporting se volvió rápido y el failover confiable porque la réplica ya no era un saco de boxeo.

Optimización que salió mal: “Añadamos un índice”

Un equipo de e-commerce tenía una consulta de reporte lenta en orders: filtro por rango de tiempo más group-by. Alguien añadió un índice en created_at y otro índice compuesto en (created_at, customer_id). La consulta fue más rápida en aislamiento, así que lo pusieron en producción y celebraron.

Dos semanas después, la latencia de escritura empezó a subir. Los inserts en orders se ralentizaron y la tasa de flush de fondo aumentó. Los nuevos índices incrementaron la amplificación de escritura—cada insert ahora mantenía más estructuras B-tree. En picos de tráfico, pagaban una “tasa de índice” en cada transacción para hacer más baratas unas pocas consultas.

Luego la herramienta BI recibió un nuevo panel que ejecutaba la misma consulta cada minuto. La consulta era más rápida, así que la concurrencia aumentó (a la gente le encanta refrescar cuando refrescar es rápido). El sistema cambió una consulta lenta por muchas medianamente rápidas y aún así quedó limitado por I/O.

La solución real fue quitar el bloat de índices, mantener OLTP ágil y construir una tabla rollup en ClickHouse actualizada continuamente. Los dashboards consultan ClickHouse. Las transacciones permanecieron fluidas. El equipo aprendió la lección: indexar no es “velocidad gratis”, es un coste en tiempo de escritura que pagas por siempre.

Práctica aburrida pero correcta que salvó el día: cuotas y backfills escalonados

Una compañía B2B SaaS usaba ClickHouse para analítica con perfiles de usuario estrictos. Los usuarios BI tenían max_execution_time, max_memory_usage y límites de concurrencia. ETL tenía límites distintos y corría en una cola controlada. A nadie le gustaban esas restricciones. Todos se beneficiaron de ellas.

Una tarde, un analista intentó ejecutar una consulta amplia sobre dos años de eventos crudos sin filtro de fecha. ClickHouse empezó a escanear, alcanzó el límite de tiempo de ejecución y mató la consulta. El analista se quejó. On-call no fue paginado. Eso es un buen intercambio.

Más tarde ese mes, el equipo de datos necesitó un backfill por un cambio de esquema en el CDC upstream. Lo planificaron por etapas: un día a la vez, verificando recuentos de parts, espacio en disco y lag después de cada bloque. Lento, cuidadoso, medible. El backfill terminó sin amenazar los dashboards de producción.

La práctica aburrida no fue un algoritmo sofisticado. Fue gobernanza y disciplina operativa: límites, colas y backfills incrementales. Les salvó porque el sistema se comportó de forma predecible cuando los humanos se comportaron de forma impredecible.

Broma #2: Lo único más permanente que un panel temporal es el canal de incidentes que crea.

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

  • Síntoma: Latencia p95 de MySQL sube durante “horas de reporting”
    Causa raíz: Escaneos largos y consultas GROUP BY compitiendo con OLTP por buffer pool e I/O
    Solución: Mueve el reporting a ClickHouse; aplica política; añade rollups curados; bloquea usuarios BI en MySQL.
  • Síntoma: El lag de la réplica de lectura aumenta cuando los analistas ejecutan reportes
    Causa raíz: I/O y CPU de la réplica saturados; el hilo SQL no puede aplicar binlog lo suficientemente rápido
    Solución: Quita acceso analítico de réplicas de failover; usa CDC a ClickHouse; limita la concurrencia de consultas.
  • Síntoma: Las consultas en ClickHouse se vuelven más lentas con el tiempo sin cambio en el tamaño de datos
    Causa raíz: Explosión de parts; merges retrasados debido a inserts fragmentados o presión de disco
    Solución: Batch de inserts; ajustar settings de merge con cuidado; monitorear parts; asegurar espacio en disco; considerar reparticionado.
  • Síntoma: Los dashboards son “rápidos a veces” y fallan con timeouts aleatorios en ClickHouse
    Causa raíz: Concurrencia BI no acotada; presión de memoria; consultas vecinas ruidosas
    Solución: Establecer límites por usuario (memoria, threads, tiempo de ejecución, concurrencia); crear tablas pre-agregadas; añadir enrutamiento de consultas.
  • Síntoma: Los datos analíticos tienen duplicados o “estado latest equivocado”
    Causa raíz: CDC aplicado como append-only sin deduplicación/versionado; updates/deletes no modelados correctamente
    Solución: Usa columnas de versión y ReplacingMergeTree donde proceda; almacena eventos y deriva el estado actual vía materialized views.
  • Síntoma: El uso de disco en ClickHouse sigue subiendo hasta convertirse en emergencia
    Causa raíz: Sin TTL; almacenamiento de crudos para siempre; backfills pesados; sin guardarraíles de capacidad
    Solución: Aplica TTL para datos fríos; downsample; archiva; comprime; aplica cuotas y procedimientos de backfill.
  • Síntoma: “Nos mudamos a ClickHouse pero MySQL sigue lento”
    Causa raíz: El pipeline CDC aún lee MySQL de forma pesada (extracts full-table, snapshots frecuentes), o la app sigue ejecutando reports en MySQL
    Solución: Usa CDC basado en binlog; revisa fuentes de consultas en MySQL; firewall/credenciales de reporting; valida con datos digest.
  • Síntoma: La frescura en ClickHouse se retrasa en picos
    Causa raíz: Cuello de botella de ingestión (throughput del pipeline), merges o presión de disco; a veces demasiados inserts pequeños
    Solución: Batch de inserts; escala la ingestión; monitorea lag; reduce temporalmente la concurrencia BI; prioriza recursos ETL.

Listas de verificación / plan paso a paso

Paso a paso: el plan de implementación de la separación limpia

  1. Declara el límite: MySQL de producción es solo OLTP. Escríbelo. Hazlo cumplir con cuentas y políticas de red.
  2. Inventaría consultas analíticas: usa tablas digest de MySQL y resúmenes del slow log para listar las 20 familias de consultas principales.
  3. Elige el método de ingestión: CDC para near-real-time; batch para diario/horario; evita la escritura dual.
  4. Define tablas analíticas en ClickHouse: empieza con tablas de eventos, particionado temporal y claves ORDER BY alineadas con filtros.
  5. Construye datasets “gold”: materialized views o tablas rollup para dashboards; conserva datos crudos para análisis profundo.
  6. Establece gobernanza desde el día uno: perfiles de usuario, cuotas, max_execution_time, max_memory_usage, max_concurrent_queries.
  7. Mide la frescura: monitorea lag de ingestión y publica el SLO a stakeholders. La gente tolera obsolescencia cuando es explícita.
  8. Corta el switch de dashboards: migra primero los dashboards de mayor impacto (los que indirectamente despiertan on-call).
  9. Bloquea la vía antigua: elimina credenciales BI de MySQL; firewall si es necesario; evita regresiones.
  10. Backfill seguro: incremental, medible, con comprobaciones de espacio en disco; nada de “corrámoslo toda la noche” sin control.
  11. Prueba carga en analítica: simula concurrencia de dashboards. ClickHouse aceptará tu optimismo y luego te lo recordará.
  12. Operacionaliza: alertas sobre recuento de parts en ClickHouse, uso de disco, fallos de consultas, lag de ingestión; y sobre latencia/I/O en MySQL.

Checklist de lanzamiento: mover un dashboard de MySQL a ClickHouse

  • ¿La consulta del dashboard incluye un filtro temporal que coincida con el particionado?
  • ¿Existe una tabla rollup/materialized view para evitar escanear eventos crudos repetidamente?
  • ¿El usuario de ClickHouse está limitado (memoria, threads, tiempo de ejecución, concurrencia)?
  • ¿La métrica de lag de CDC es visible para los usuarios del dashboard?
  • ¿La consulta antigua en MySQL está bloqueada o al menos eliminada de la app/herramienta BI?
  • ¿Validaste los resultados para un periodo de tiempo conocido (comprobar totales y recuentos)?

Checklist operativo: higiene semanal que previene desastres lentos

  • Revisar las parts activas en ClickHouse por tabla; investigar crecimientos rápidos.
  • Revisar el espacio en disco de ClickHouse; mantener suficiente espacio libre para merges y backfills.
  • Revisar las consultas BI principales por read_rows y uso de memoria; optimizar o pre-agregar.
  • Revisar los top digests de MySQL para asegurar que la analítica no haya regresado.
  • Probar rutas de restauración: backups de MySQL, expectativas de recuperación de metadata y datos de ClickHouse.

Preguntas frecuentes

1) ¿No puedo simplemente escalar MySQL verticalmente y listo?

Puedes, y obtendrás alivio temporal. El modo de fallo vuelve cuando aparece el siguiente dashboard o consulta de cohortes. El problema es la desalineación de carga, no solo la potencia.

2) Si ya tengo réplicas de MySQL—¿debería apuntar BI a ellas?

Sólo si estás cómodo con el lag y no usas esas réplicas para failover. Incluso entonces, limita la concurrencia y trátalo como un puente temporal, no como el estado final.

3) ¿ClickHouse es lo suficientemente “tiempo real” para dashboards operativos?

Muchas veces sí, con CDC. Mide el lag de ingestión explícitamente y diseña dashboards para tolerar pequeños retrasos. Si necesitas verdad transaccional sub-segundo, ese es territorio de MySQL.

4) ¿Cómo manejo updates y deletes de MySQL en ClickHouse?

Prefiere el modelado por eventos (append). Si necesitas “estado actual”, usa filas versionadas con motores como ReplacingMergeTree y diseña consultas/materialized views en consecuencia.

5) ¿ClickHouse reemplazará mi data warehouse?

A veces. Para muchas compañías se convierte en el almacén analítico principal. Pero si necesitas transformaciones pesadas, gobernanza o modelado entre sistemas, puede que aún mantengas una capa de warehouse. No fuerces una conversión religiosa.

6) ¿Cuál es la ganancia más rápida si estamos en llamas hoy?

Deja de ejecutar analítica en MySQL inmediatamente: mata las peores consultas, remueve acceso BI y mueve el dashboard a ClickHouse o a un rollup en caché. Luego arréglalo correctamente.

7) ¿Cuál es la mayor sorpresa operativa de ClickHouse para equipos MySQL?

Merges y parts. Los acostumbrados a row-store esperan “lo inserté, ya está.” ClickHouse sigue trabajando en segundo plano y debes monitorear ese trabajo.

8) ¿Cómo evito que los analistas escriban consultas caras en ClickHouse?

Usa perfiles de usuario con cuotas y timeouts, proporciona tablas “gold” curadas y enseña a la gente que faltar filtros temporales no es “explorar”, es incendio intencionado.

9) ¿Las materialized views lo solucionan todo?

No. Son excelentes para rollups estables y agregados comunes. Pero pueden añadir complejidad y coste de almacenamiento. Úsalas donde reduzcan trabajo repetido de forma medible.

10) ¿Y si mis consultas analíticas requieren joins complejos entre muchas tablas?

Desnormaliza para los caminos comunes, pre-calcula dimensiones y limita los joins. ClickHouse puede hacer joins, pero los mejores sistemas analíticos de producción evitan hacerlo repetidamente en tiempo de consulta.

Conclusión: pasos prácticos siguientes

Si haces una sola acción esta semana, haz esta: quita la carga analítica de MySQL. No pidiéndole a los usuarios que “tengan cuidado”, sino proporcionando un lugar mejor para hacer preguntas.

  1. Bloquea MySQL: cuentas separadas, bloquear redes BI y hacer cumplir que MySQL de producción atienda primero a los usuarios.
  2. Establece gobernanza en ClickHouse: límites, cuotas y datasets curados antes de invitar a toda la compañía.
  3. Mueve las 5 consultas peores: replica los datos necesarios vía CDC o batch, luego construye rollups para que los dashboards sigan siendo baratos.
  4. Operacionaliza la frescura: publica el lag de ingestión y trátalo como un requisito de producto. Es mejor estar sinceramente 60 segundos detrás que desconocidamente equivocado.
  5. Practica backfills: por etapas, medibles y reversibles. Tu yo futuro apreciará la contención de tu yo presente.

La separación limpia no es glamorosa. Es simplemente la diferencia entre una base de datos que sirve clientes y una base de datos que alberga una pelea diaria de analítica. Elige la vida más tranquila.

SLI/CrossFire: Por qué el multi-GPU fue un sueño — y por qué murió

Si alguna vez intentaste «simplemente añadir otra GPU» y esperaste que la gráfica subiera sin más, ya has conocido al villano de esta historia:
el mundo real. El multi‑GPU en los juegos de consumo —NVIDIA SLI y AMD CrossFire— parecía pura justicia de ingeniería: paralelismo,
más silicio, más frames, listo.

Luego lo lanzaste. Los tiempos de fotograma se convirtieron en una valla. La pila de controladores pasó a ser una negociación entre el motor del juego, el planificador de GPU, PCIe
y el timing del monitor que creías entender. Tu costosa segunda tarjeta a menudo se convirtió en un radiador con currículum.

La promesa: escalar añadiendo GPUs

El multi‑GPU, tal como se vendía a los jugadores, era un cuento operativo: tu juego está limitado por la GPU, por lo tanto otra GPU significa casi el doble de rendimiento.
Ese es el argumento. También es la primera suposición equivocada. Los sistemas no escalan porque una diapositiva de marketing diga «2×»; los sistemas escalan cuando la parte más lenta
del pipeline deja de ser la más lenta.

Un frame moderno de un juego es una línea de ensamblaje desordenada: simulación de CPU, envío de draw calls, renderizado por GPU, post‑procesado, composición, presentación,
y un contrato de sincronización con tu pantalla. SLI/CrossFire intentó ocultar la complejidad multi‑GPU detrás de controladores, perfiles y un puente. Ese ocultamiento es
exactamente lo que lo condenó.

El sueño del multi‑GPU murió porque chocó contra la física (latencia y sincronización), la economía del software (los desarrolladores no prueban configuraciones raras) y
los cambios de plataforma (DX12/Vulkan trasladaron la responsabilidad del driver al motor). Y porque el «FPS medio» resultó ser una omisión: lo que perciben tus ojos es la consistencia de los tiempos de fotograma, no la media.

Cómo funcionaba realmente SLI/CrossFire

Multi‑GPU gestionado por el controlador: perfiles hasta el fondo

En la era clásica, SLI/CrossFire dependía de heurísticas del controlador y perfiles por juego. El controlador decidía cómo repartir el renderizado entre las GPUs
sin que el juego lo supiera explícitamente. Eso suena conveniente. También es una pesadilla operativa: ahora tienes un sistema distribuido donde un nodo
(el juego) no sabe que está distribuido.

Los perfiles importaban porque la mayoría de los juegos no estaban escritos para paralelizarse de forma segura entre GPUs. El controlador necesitaba «pistas» específicas del juego para evitar
peligros como leer datos que aún no se habían producido, o aplicar post‑procesados que asumen un historial completo de frames.

Los modos principales: AFR, SFR y «por favor no hagan eso»

Alternate Frame Rendering (AFR) fue el caballo de batalla. GPU0 renderiza el frame N, GPU1 renderiza el frame N+1, repetir. En papel: fantástico.
En la práctica: AFR es una máquina de latencia y pacing. Si el frame N tarda 8 ms y el frame N+1 tarda 22 ms, tu «FPS medio» puede parecer correcto mientras tus
ojos reciben un pase de diapositivas con pasos extra.

Split Frame Rendering (SFR) divide un solo frame en regiones. Esto exige un balanceo de carga cuidadoso: una mitad de la pantalla puede
contener una explosión, sombreado de pelo, volumétricos y tus remordimientos; la otra mitad es una pared. Adivina qué GPU termina primero y se queda en espera.

También hubo modos híbridos y trucos específicos de cada proveedor. Cuantos más trucos necesites, menos general será tu solución. En algún punto ya no estás
ofreciendo «soporte multi‑GPU»; estás escribiendo respuesta a incidentes por título dentro del controlador.

Puentes, PCIe y por qué el interconector nunca fue el héroe

Los puentes SLI (y los puentes CrossFire en eras anteriores) ofrecían un camino de mayor ancho de banda y menor latencia para ciertas operaciones de sincronización y compartición de buffers
que PCIe por sí solo. Pero el puente no fusionaba mágicamente la VRAM. Cada GPU seguía teniendo su propia memoria. En AFR, cada GPU típicamente necesitaba su
propia copia de las mismas texturas y geometría. Así que tus «dos tarjetas de 8 GB» no se convertían en «16 GB». Se convertían en «8 GB, dos veces».

Cuando los desarrolladores comenzaron a apoyarse más en técnicas temporales—TAA, reflexiones en espacio de pantalla con buffers históricos, escaladores temporales—AFR se volvió
cada vez más incompatible. No puedes renderizar fácilmente el frame N+1 en GPU1 si necesita historial del frame N que vive en GPU0, a menos que añadas
sincronización y transferencia de datos que borran la ganancia de rendimiento.

Una idea parafraseada, ampliamente atribuida en espíritu al pensamiento de confiabilidad de sistemas (y a menudo mencionada por ingenieros en el órbita SRE de Google): Idea parafraseada: la esperanza no es una estrategia.
Encaja perfectamente con el multi‑GPU. SLI/CrossFire te pedía esperar que el pipeline de renderizado de tu juego se alineara con las suposiciones del controlador.

Por qué falló: la muerte por mil casos límite

1) El pacing de frames mató el «se siente rápido»

AFR puede entregar un alto FPS medio mientras produce tiempos de fotograma desiguales (microstutter). Los humanos notan la varianza. Tu overlay de monitor puede mostrar
«120 FPS», mientras tu cerebro registra «inconsistente». Este fue el fallo central de experiencia de usuario: SLI/CrossFire podía ganar en benchmarks y perder
frente a los ojos.

El pacing de frames no es solo «un poco de jitter». Interactúa con VSync, VRR (G‑SYNC/FreeSync), profundidad de cola de renderizado y la planificación de la CPU. Si el controlador
encola frames demasiado agresivamente, obtienes latencia de entrada. Si encola muy poco, obtienes burbujas y stutter.

Broma #1: El multi‑GPU es como tener dos becarios que escriben páginas alternadas del mismo informe: rápido, hasta que notas que discrepan en la trama.

2) Espejado de VRAM: pagaste por memoria que no podías usar

En el consumo, el multi‑GPU casi siempre espejaba activos en la memoria de cada GPU. Eso permitió escalado sin tratar la memoria como un pool coherente compartido,
pero también significó que texturas de alta resolución, geometría grande y estructuras de aceleración de trazado moderno quedaron limitadas por la menor
VRAM en una sola tarjeta.

A medida que los juegos demandaban más VRAM, el plan de «añadir una segunda GPU» empeoró: tu cuello de botella se trasladó del cómputo a la capacidad de memoria, y el multi‑GPU
no ayudó. Peor aún, una segunda GPU aumentó consumo eléctrico, calor y necesidad de flujo de aire en el chasis mientras entregaba el mismo límite de VRAM que una sola tarjeta.

3) La CPU se convirtió en coordinadora, y tampoco escaló

Multi‑GPU no es solo «dos GPUs». Es trabajo extra del controlador, gestión adicional de command buffers, más sincronización y a menudo más overhead de draw calls.
Muchos motores ya estaban limitados por la CPU en el hilo de render. Añadir una segunda GPU puede desplazar el cuello de botella hacia arriba y convertir a la CPU en el limitante.

En términos productivos: añadiste capacidad a un servicio downstream sin aumentar el throughput upstream. Felicitaciones, inventaste una nueva cola.

4) El modelo de perfiles del controlador no sobrevivió a la cadena de suministro de software

SLI/CrossFire gestionado por controlador requería que los proveedores mantuvieran el ritmo con nuevos lanzamientos de juegos, parches, actualizaciones de motores y nuevas técnicas de render.
Los estudios publicaban actualizaciones semanales. Los vendedores de GPU sacaban controladores en una cadencia más lenta y tenían que probar miles de combinaciones.

Un perfil multi‑GPU que funciona en la versión 1.0 puede romperse en la 1.0.3 porque cambió el orden de un pase de post‑procesado, o porque un nuevo filtro temporal ahora
lee un buffer del frame anterior. El «optimizar» del controlador a ciegas puede volverse aquello que corrompe el frame.

5) VRR (frecuencia variable) y multi‑GPU se hicieron la vida imposible

La frecuencia de refresco variable es una de las mejores mejoras de calidad de vida en el juego de PC. También complica el pacing multi‑GPU: la pantalla se adapta al
ritmo de entrega de frames, así que si AFR crea ráfagas y huecos, VRR no puede «suavizarlos»; mostrará fielmente la desigualdad.

Muchos usuarios actualizaron a monitores VRR y descubrieron que su configuración multi‑GPU previamente «aceptable» ahora se veía peor. No es culpa del monitor.
Es que por fin estás viendo la verdad.

6) Llegó el multi‑GPU explícito, y la industria no quiso pagar la factura

DX12 y Vulkan hicieron posible el multi‑adaptador explícito: el motor puede controlar múltiples GPUs directamente. Eso es técnicamente más limpio que la magia del controlador.
También es trabajo de ingeniería costoso que beneficia a una fracción minúscula de clientes.

Los estudios priorizaron características que se enviaban a todos: mejores escaladores temporales, mejor antialiasing, mejores pipelines de contenido, mejor paridad con consolas.
El multi‑GPU era una carga de soporte con bajo ROI. Murió como muchas funciones empresariales: en silencio, porque nadie financió la rotación de on‑call.

7) Energía, térmicas y limitaciones de caja: la capa física se defendió

Dos GPUs de gama alta demandan un margen serio de PSU, buen flujo de aire y a menudo una placa base que pueda proveer suficientes líneas PCIe sin estrangulamiento. La
«caja de consumo + dos GPUs tope de gama» es un proyecto de ingeniería térmica. Y la mayoría de la gente quería un ordenador, no un hobby que queme polvo.

8) Seguridad y estabilidad: la pila de controladores se volvió un radio de explosión mayor

Cuanto más compleja sea la lógica de planificación del controlador y la sincronización entre GPUs, más modos de fallo: pantallas negras, TDRs (timeout detection and recovery),
corrupciones extrañas, crashes específicos por juego. En términos operativos, aumentaste la complejidad del sistema y redujiste el tiempo medio hasta la inocencia.

Broma #2: SLI prometía «dos GPUs», pero a veces entregaba «el doble de resolución de problemas», lo cual no es una característica que nadie mida en benchmarks.

Contexto histórico: hechos que la gente olvida

  • Hecho 1: El nombre original «SLI» vino de Scan‑Line Interleave de 3dfx a finales de los 90; NVIDIA reutilizó el acrónimo más tarde con un enfoque técnico diferente.
  • Hecho 2: El multi‑GPU de consumo temprano a menudo se apoyó mucho en AFR porque era la manera más fácil de escalar sin reescribir motores.
  • Hecho 3: El escalado multi‑GPU fue notoriamente inconsistente: algunos títulos vieron ganancias casi lineales, otros cero, y algunos se volvieron más lentos por overhead de CPU/controlador.
  • Hecho 4: «Microstutter» se convirtió en una queja general a principios de los 2010s cuando los analistas comenzaron a medir tiempos de fotograma en lugar del FPS medio.
  • Hecho 5: AMD invirtió en mejoras de pacing de frames en controladores tras críticas generalizadas; ayudó, pero no cambió las limitaciones subyacentes de AFR.
  • Hecho 6: Muchos motores utilizaron cada vez más buffers de historial temporal (TAA, escalado temporal, vectores de movimiento), que son inherentemente incómodos para AFR.
  • Hecho 7: El ancho de banda de PCIe subió con las generaciones, pero la latencia y el overhead de sincronización siguieron siendo problemas centrales para dependencias frame‑a‑frame.
  • Hecho 8: El multi‑GPU explícito de DX12/Vulkan puso el control en la aplicación; la mayoría de los estudios optó por no implementarlo porque la matriz de pruebas explotaba.
  • Hecho 9: NVIDIA restringió/cambió gradualmente el soporte SLI en generaciones posteriores, enfocándose en segmentos de gama alta y casos de uso específicos en vez de soporte amplio por título.

Qué lo reemplazó (más o menos): multi‑GPU explícito y alternativas modernas

Multi‑GPU explícito: mejor arquitectura, peor economía

El multi‑GPU explícito (multi‑adaptador de DX12, device groups de Vulkan) es como lo diseñarías si estuvieras sobrio: el motor sabe qué cargas pueden ejecutarse en
qué GPU, qué datos necesitan compartirse y cuándo sincronizar. Esto elimina mucha suposición del controlador.

También exige que el motor esté estructurado para paralelismo entre dispositivos: duplicación de recursos, barreras entre dispositivos, manejo cuidadoso de efectos temporales,
y estrategias diferentes para distintas combinaciones de GPU. Eso no es «soportar SLI». Es construir un segundo renderizador.

Algunos títulos lo experimentaron. La mayoría de los estudios hizo las cuentas y compró otra cosa: escaladores temporales, mejor threading de CPU y optimizaciones de contenido que ayudan a todos los usuarios.

El «multi‑GPU» moderno que realmente funciona: especialización

El multi‑GPU sigue vivo donde la carga es naturalmente paralela y no requiere coherencia estricta frame a frame:

  • Renderizado offline / path tracing: Puedes dividir muestras o teselas entre GPUs y fusionar resultados.
  • Cómputo / entrenamiento ML: Paralelismo de datos con frameworks explícitos, aunque todavía con dolores de sincronización.
  • Pipelines de codificación de vídeo: GPUs separadas pueden manejar flujos o etapas separadas.

Para el juego en tiempo real, la estrategia ganadora fue: una GPU potente, mejor programación, mejor escalado y mejores técnicas de generación de frames. No
porque sea «cool», sino porque es operacionalmente sensato.

Guía rápida de diagnóstico

Cuando alguien dice «mi segunda GPU no hace nada» o «SLI lo empeoró», no empieces con palancas místicas del controlador. Trátalo como un incidente.
Establece qué está limitado y luego aisla.

Primero: confirma que el sistema ve ambas GPUs y que el enlace está sano

  • ¿Ambos dispositivos aparecen en PCIe?
  • ¿Funcionan a la generación/anchura PCIe esperada?
  • ¿Está instalado el puente correcto (si se requiere)?
  • ¿Los conectores de alimentación son correctos y estables?

Segundo: confirma que la ruta de software es realmente multi‑GPU

  • ¿Se sabe que el juego soporta SLI/CrossFire para tu generación de GPU?
  • ¿El perfil del controlador está presente/habilitado?
  • ¿La vía de la API (DX11 vs DX12 vs Vulkan) es compatible con el modo multi‑GPU del proveedor?

Tercero: mide los tiempos de fotograma e identifica el recurso limitante

  • Utilización por GPU (no solo «total»).
  • Saturación del hilo de render de la CPU.
  • Uso de VRAM y comportamiento de paginación.
  • Pacing de frames (percentil 99 del tiempo de fotograma), no solo FPS medio.

Cuarto: elimina variables hasta que el comportamiento sea explicable

  • Desactiva VRR/VSync temporalmente para observar el pacing bruto.
  • Prueba un título/benchmark conocido con escalado documentado.
  • Prueba cada GPU individualmente para descartar una tarjeta marginal.

Tareas prácticas: comandos, salidas y decisiones

Estas asumen una estación Linux usada para pruebas/CI, reproducción en laboratorio, o simplemente porque disfrutas del dolor de forma reproducible. El punto no es
que Linux sea donde el SLI para juegos alcanzó su cúspide; es que Linux te da observabilidad sin una búsqueda del tesoro en GUI.

Tarea 1: Listar GPUs y confirmar la topología PCIe

cr0x@server:~$ lspci -nn | egrep -i 'vga|3d|display'
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP102 [GeForce GTX 1080 Ti] [10de:1b06]
02:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP102 [GeForce GTX 1080 Ti] [10de:1b06]

Qué significa: Dos GPUs se enumeran en el bus PCIe. Si solo ves una, detente: tienes un problema de hardware/firmware.

Decisión: Si falta una GPU, vuelca, revisa cables de alimentación, ajustes BIOS (Above 4G decoding, configuración de slots PCIe), y vuelve a probar.

Tarea 2: Verificar ancho y generación del enlace PCIe para cada GPU

cr0x@server:~$ sudo lspci -s 01:00.0 -vv | egrep -i 'LnkCap|LnkSta'
LnkCap: Port #0, Speed 8GT/s, Width x16
LnkSta: Speed 8GT/s, Width x16

Qué significa: La GPU está negociando PCIe Gen3 x16 como se espera. Si ves x8 o Gen1, has encontrado un cuello de botella o fallback.

Decisión: Si el enlace está degradado, revisa el cableado de slots, el reparto de líneas de la placa base (M.2 que roba líneas), BIOS PCIe, risers y la integridad de la señal.

Tarea 3: Confirmar que el controlador NVIDIA ve ambas GPUs e informa utilización

cr0x@server:~$ nvidia-smi -L
GPU 0: GeForce GTX 1080 Ti (UUID: GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)
GPU 1: GeForce GTX 1080 Ti (UUID: GPU-ffffffff-1111-2222-3333-444444444444)

Qué significa: La capa del controlador ve ambos dispositivos. Si uno falta aquí pero aparece en lspci, probablemente tienes un problema de binding del controlador o incompatibilidad de firmware.

Decisión: Si falta, revisa dmesg en busca de errores de GPU, verifica módulos del kernel y confirma que ambas GPUs son soportadas por el driver instalado.

Tarea 4: Observar utilización y memoria por GPU durante carga

cr0x@server:~$ nvidia-smi dmon -s pucvmet
# gpu   pwr gtemp mtemp    sm   mem   enc   dec  mclk  pclk   pviol  rxpci  txpci
    0   210    78     -    92    55     0     0  5500  1582      0    120    110
    1    95    64     -    18    52     0     0  5500  1582      0     40     35

Qué significa: GPU0 está realizando trabajo real; GPU1 está mayormente inactiva pero aún mantiene VRAM similar (espejado de activos). Ese es el comportamiento clásico de «segunda GPU no utilizada».

Decisión: Si GPU1 permanece inactiva, verifica que la vía de la aplicación soporte multi‑GPU; de lo contrario, deja de intentar arreglar una no‑característica.

Tarea 5: Confirmar detalles de sesión Xorg/Wayland (para evitar sorpresas del compositor)

cr0x@server:~$ echo $XDG_SESSION_TYPE
wayland

Qué significa: Estás en Wayland. Algunas herramientas y ciertas rutas legacy de multi‑GPU se comportan diferente bajo Wayland vs Xorg.

Decisión: Si depuras problemas de render/presentación, reproduce bajo Xorg como control para aislar efectos de timing del compositor.

Tarea 6: Revisar logs del kernel por errores PCIe y resets de GPU

cr0x@server:~$ sudo dmesg -T | egrep -i 'pcie|aer|nvrm|gpu|xid' | tail -n 12
[Mon Jan 13 10:19:22 2026] NVRM: Xid (PCI:0000:02:00): 79, GPU has fallen off the bus.
[Mon Jan 13 10:19:22 2026] pcieport 0000:00:03.1: AER: Corrected error received: 0000:02:00.0

Qué significa: «Fallen off the bus» a menudo indica inestabilidad de alimentación/temperaturas, riser defectuoso, slot inestable o problemas de integridad de señal—el multi‑GPU hace esto más probable.

Decisión: Trátalo como fiabilidad de hardware: reduce el límite de potencia, mejora la refrigeración, vuelve a montar, intercambia slots, elimina risers, actualiza BIOS y prueba estabilidad antes de culpar al driver.

Tarea 7: Comprobar indicadores de cuello de botella de CPU (carga, cola de ejecución, throttling)

cr0x@server:~$ uptime
 10:22:11 up 3 days,  6:41,  1 user,  load average: 14.82, 13.97, 12.10

Qué significa: Un load average alto puede indicar saturación de CPU o hilos ejecutables acumulándose. Los juegos pueden estar limitados por la CPU en un solo hilo de render incluso si la CPU total no está «al 100%».

Decisión: Si la carga es alta y la utilización GPU es baja, deja de perseguir ajustes SLI. Reduce ajustes pesados de CPU (distancia de visión, densidad de población), o acepta que estás limitado por la CPU.

Tarea 8: Inspeccionar uso por núcleo para detectar un hilo de render pegado

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

10:22:18 AM  CPU   %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
10:22:19 AM  all   42.0  0.0  8.0   0.2    0.0  0.5    0.0    0.0    0.0   49.3
10:22:19 AM    3   98.5  0.0  1.0   0.0    0.0  0.0    0.0    0.0    0.0    0.5

Qué significa: Un núcleo (CPU3) está saturado. Ese es tu cuello de botella de render/juego. Dos GPUs no ayudarán si el frame no puede ser alimentado.

Decisión: Reduce ajustes limitados por CPU, o cambia a una CPU/plataforma con mejor rendimiento de hilo único. Multi‑GPU no arreglará un tubo estrecho upstream.

Tarea 9: Verificar presión de memoria (la paginación puede hacerse pasar por «stutter de GPU»)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            32Gi        30Gi       500Mi       1.2Gi       1.5Gi       1.0Gi
Swap:           16Gi        10Gi       6.0Gi

Qué significa: Estás haciendo mucho swap. Eso destruirá los tiempos de fotograma sin importar cuántas GPUs apiles.

Decisión: Arregla la presión de memoria primero: cierra apps en segundo plano, reduce ajustes de textura, añade RAM y vuelve a probar. Trata el uso de swap como alarma roja para el pacing.

Tarea 10: Confirmar frecuencia de CPU y estado de throttling

cr0x@server:~$ lscpu | egrep -i 'model name|cpu mhz'
Model name:                           AMD Ryzen 9 5950X 16-Core Processor
CPU MHz:                               3599.998

Qué significa: La frecuencia actual se muestra, pero no si está haciendo throttling bajo carga sostenida.

Decisión: Si los relojes bajan bajo carga de juego, arregla la refrigeración o los límites de potencia. Multi‑GPU aumenta el calor en la caja, lo que puede reducir silenciosamente el boost de la CPU.

Tarea 11: Revisar flags de capping / throttling de potencia en NVIDIA

cr0x@server:~$ nvidia-smi -q -d PERFORMANCE | egrep -i 'Power Limit|Clocks Throttle Reasons' -A3
    Power Limit                        : 250.00 W
    Clocks Throttle Reasons
        Idle                           : Not Active
        Applications Clocks Setting     : Not Active
        SW Power Cap                   : Active

Qué significa: La GPU está alcanzando un tope de potencia por software. En multi‑GPU, PSUs y térmicas de VRM pueden forzar límites conservadores.

Decisión: Si el tope de potencia está activo y el rendimiento es inconsistente, considera bajar el objetivo de FPS, mejorar el flujo de aire o ejecutar una sola GPU a relojes sostenidos más altos.

Tarea 12: Comprobar pistas de compartición de líneas PCIe desde el estado NVLink/bridge (cuando esté disponible)

cr0x@server:~$ nvidia-smi topo -m
        GPU0    GPU1    CPU Affinity
GPU0     X      PHB     0-7
GPU1    PHB      X      8-15

Qué significa: PHB indica que la comunicación pasa por el puente host PCIe; no es un camino peer‑to‑peer estrecho. Eso puede perjudicar cualquier carga que necesite tráfico entre GPUs.

Decisión: Si la topología es pobre, deja de esperar que la colaboración entre GPUs sea barata. Prefiere renderizado en una sola GPU o particionado explícito de la carga que evite compartir.

Tarea 13: Confirmar visibilidad de dispositivos en Vulkan (para experimentos multi‑GPU explícitos)

cr0x@server:~$ vulkaninfo --summary | egrep -i 'GPU id|deviceName' -A1
GPU id : 0 (GeForce GTX 1080 Ti)
deviceName     = GeForce GTX 1080 Ti
GPU id : 1 (GeForce GTX 1080 Ti)
deviceName     = GeForce GTX 1080 Ti

Qué significa: Vulkan ve ambos dispositivos. Eso es un prerrequisito para apps multi‑GPU explícitas, no una garantía de que algún juego lo soporte.

Decisión: Si solo aparece uno, arregla la instalación del driver/runtime. Si ambos aparecen, pasa a las comprobaciones a nivel de aplicación.

Tarea 14: Validar latencia de almacenamiento (sí, puede parecer «stutter de GPU»)

cr0x@server:~$ iostat -xz 1 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          38.12    0.00    6.21    8.93    0.00   46.74

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s w_await aqu-sz  %util
nvme0n1         210.0   38400.0     0.0   0.00   3.10   182.86    75.0   10240.0   9.80   2.10  78.00

Qué significa: Alto iowait y await elevado pueden causar hitches en el streaming. Multi‑GPU no arreglará compilaciones de shaders ni latencia de streaming de activos.

Decisión: Si el almacenamiento está saturado, reduce IO en segundo plano, mueve el juego a almacenamiento más rápido y arregla el comportamiento de caché de shaders. Arregla el cuello de botella real.

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

1) «La segunda GPU muestra 0–10% de utilización»

Síntomas: Una GPU se calienta, la otra está inactiva; FPS sin cambios respecto a una sola GPU.

Causa raíz: La vía del juego/API no soporta multi‑GPU gestionado por el controlador, o el perfil del controlador está ausente/deshabilitado.

Solución: Valida el soporte del título para tu generación de GPU y modo de API. Si el juego es DX12/Vulkan y no implementa multi‑GPU explícito, acepta una sola GPU.

2) «Mayor FPS medio, pero se siente peor»

Síntomas: El benchmark dice más rápido; la jugabilidad se siente con stutter; VRR lo hace más evidente.

Causa raíz: Varianza de tiempos de fotograma por AFR (microstutter), encolamiento o carga por frame inconsistente.

Solución: Mide los tiempos de fotograma y limita FPS para estabilizar el pacing, o desactiva multi‑GPU. Prioriza 1% low / percentil 99 del tiempo de fotograma sobre medias.

3) «Las texturas aparecen de golpe y el hitching se vuelve brutal en 4K»

Síntomas: Picos repentinos, especialmente al girar rápido o entrar en áreas nuevas.

Causa raíz: El límite de VRAM es por GPU; el espejado significa que no ganaste capacidad. Estás paginando activos y bloqueando.

Solución: Baja la resolución de texturas, reduce ajustes de RT, o cambia a una sola GPU con más VRAM.

4) «Pantallas negras aleatorias / GPU desapareció»

Síntomas: Reseteos del controlador, una GPU desaparece del bus, problemas de estabilidad intermitentes.

Causa raíz: Inestabilidad en la entrega de potencia, estrés térmico, integridad marginal de PCIe o un overclock que era «estable» en una tarjeta.

Solución: Vuelve a relojes de stock, reduce el límite de potencia, mejora la refrigeración, verifica el cableado, evita risers, actualiza BIOS y prueba cada GPU sola.

5) «Funciona en una versión de driver, falla en la siguiente»

Síntomas: El escalado desaparece o aparecen artefactos después de actualizar controladores.

Causa raíz: Cambios en perfiles, cambios de scheduling o regresión en caminos de código multi‑GPU (ahora de baja prioridad).

Solución: Fija versiones de controladores para tu caso de uso, documenta combinaciones conocidas buenas y no trates «último controlador» como inherentemente mejor para multi‑GPU.

6) «Dos GPUs, pero el uso de CPU se ve bajo—aún limitado por CPU»

Síntomas: Utilización de GPU baja, FPS topeado, CPU total por debajo del 50%.

Causa raíz: Uno o dos hilos calientes (render thread, game thread). La CPU total oculta saturación por núcleo.

Solución: Observa uso por núcleo. Reduce ajustes pesados de CPU; busca tiempos de fotograma estables; considera actualizar plataforma en lugar de añadir GPUs.

7) «PCIe x8/x4 inesperado, escalado pobre»

Síntomas: Escalado peor del esperado; stutter alto durante el streaming; topo muestra rutas PHB.

Causa raíz: Compartición de líneas con M.2/otros dispositivos, elección de slot incorrecta o limitaciones del uplink del chipset.

Solución: Usa los slots correctos, reduce consumidores de líneas o elige una plataforma con más líneas de CPU si insistes en configuraciones multi‑dispositivo.

Tres micro‑historias corporativas desde el terreno

Micro‑historia 1: El incidente causado por una suposición errónea

Un estudio pequeño tenía un «laboratorio de rendimiento» con unas pocas máquinas de pruebas de gama alta. Alguien había montado una máquina monstruo: dos GPUs tope, mucho RGB y una
hoja de cálculo de números de benchmark que alegraba a la dirección. El estudio la usó para aprobar presupuestos de rendimiento para un nuevo nivel cargado de contenido.

La suposición errónea fue sutil: asumieron que el escalado era representativo. Su máquina de aprobación ejecutaba AFR con un perfil de controlador que casualmente funcionaba bien para esa build específica. Produjo un gran FPS medio en el laboratorio. No produjo buenos tiempos de fotograma en la mayoría de las máquinas de los usuarios, y definitivamente no representó la línea base de una sola GPU que poseía la mayoría.

Llegó la semana de lanzamiento. Las redes se llenaron de quejas de «stutter en el nuevo nivel». Internamente, la máquina del laboratorio parecía «bien». Los ingenieros comenzaron
a perseguir bugs fantasma en animación y física porque los gráficos de GPU no parecían saturados.

El verdadero culpable fue el streaming de activos más un nuevo efecto temporal. En la máquina del laboratorio, AFR enmascaró parte del tiempo de GPU solapando trabajo, mientras empeoraba el pacing de una forma que el estudio no medía. En las máquinas de los consumidores con una sola GPU, el mismo efecto empujó la VRAM al límite y desencadenó paginación y thrash de caché de shaders. El estudio había optimizado para la realidad equivocada.

La solución no fue un ajuste mágico de multi‑GPU. Reconstruyeron su gate de rendimiento: una sola GPU, medición basada en tiempos de fotograma, con umbrales de presión de memoria. La máquina dual se quedó en el laboratorio, pero dejó de ser la fuente de la verdad. El incidente terminó cuando dejaron de confiar en un benchmark que no coincidía con la población de usuarios.

Micro‑historia 2: La optimización que se volvió en contra

Un equipo de visualización empresarial (pensemos: escenas CAD grandes, walkthroughs en tiempo real) intentó «obtener rendimiento gratis» activando AFR en un entorno controlado. Sus escenas
dependían mucho de acumulación temporal: anti‑aliasing, denoise y mucha lógica de «usar el frame anterior». Alguien argumentó que, dado que las GPUs eran idénticas, los resultados deberían ser consistentes.

Obtuvieron mayor throughput medio con cámara estática. Gran demo. Luego enviaron una beta a algunos stakeholders internos. En cuanto movías la cámara, la estabilidad de imagen se degradó: ghosting, shimmer y filtros temporales inconsistentes. Peor aún, la latencia interactiva se sintió peor porque la profundidad de cola aumentó bajo AFR.

El retroceso fue arquitectónico: el pipeline temporal del renderer asumía un historial de frames coherente. AFR dividió ese historial entre dispositivos. El equipo agregó puntos de sincronización y transferencias entre GPUs para «arreglarlo», lo que destruyó la ganancia de rendimiento e introdujo nuevos stalls. Ahora tenían complejidad y sin aumento de velocidad.

Eventualmente eliminaron AFR e invirtieron en un conjunto aburrido pero efectivo de mejoras: culling en CPU, simplificación de shaders y reglas de LOD de contenido. El sistema final fue más rápido en una sola GPU que la build AFR en dos. La optimización falló porque optimizó la capa equivocada: intentó paralelizar algo que era fundamentalmente serial en términos de dependencia temporal.

Micro‑historia 3: La práctica aburrida pero correcta que salvó el día

Un grupo de validación de hardware en una empresa mediana mantenía una flota de nodos de prueba GPU. No jugaban en ellos; ejecutaban regresiones de render y compute y ocasionalmente reproducían bugs de clientes. Los nodos incluían cajas multi‑GPU porque los clientes las usaban para cómputo, no porque fuera divertido.

Su arma secreta no era un scheduler ingenioso. Era un changelog. Cada nodo tenía una versión de driver fijada, una baseline de firmware fijada y una matriz simple de «conocido‑bueno». Las actualizaciones se desplegaban por etapas: un nodo canario primero, luego un lote pequeño y luego el resto. Sin excepciones. A nadie le gustaba. Parecía lento.

Una semana, un driver nuevo introdujo errores correctables PCIe en una revisión específica de placa base cuando ambas GPUs estaban bajo carga mixta. En el workstation de un desarrollador parecía crash aleatorio de aplicaciones. En la flota, el nodo canario empezó a emitir logs AER en horas.

Porque el grupo tenía disciplina aburrida, correlacionaron la línea de tiempo, revertieron el canario y bloquearon el despliegue. No hubo inestabilidad en la flota, no hubo reimaging masivo, no hubo scramble. Abrieron un ticket con el proveedor con logs reproducibles y una receta de reproducción precisa.

La «salvación» no fue depuración heroica. Fue la práctica operacional de despliegues por etapas y fijado de versiones. Los sistemas multi‑GPU amplifican problemas marginales; la única respuesta sensata es tratar los cambios como cambios de producción, no como experimentos de fin de semana.

Listas de verificación / plan paso a paso

Paso a paso: decidir si vale la pena tocar multi‑GPU

  1. Define el objetivo. ¿Es mayor FPS medio, mejores 1% lows o una carga de cómputo/render específica?
  2. Identifica el tipo de carga. ¿Tiempo real con efectos temporales? Asume «no». ¿Render offline/cómputo? Quizá «sí».
  3. Comprueba la realidad de soporte. Si la app no implementa multi‑GPU explícito y el proveedor ya no soporta perfiles de controlador, para aquí.
  4. Mide la baseline. Una sola GPU, driver estable, tiempos de fotograma, uso de VRAM, CPU por núcleo.
  5. Añade la segunda GPU. Verifica ancho de enlace PCIe, potencia, térmicas y topología.
  6. Re‑mide. Busca mejoras en el percentil 99 del tiempo de fotograma y throughput, no solo en el FPS medio.
  7. Decide. Si las ganancias son pequeñas o el pacing empeora, retíralo. El impuesto de complejidad es real.

Paso a paso: estabilizar una máquina multi‑GPU (cuando debes mantenerla)

  1. Arranca con relojes de stock. Los overclocks que son «estables» en una GPU pueden fallar en condiciones térmicas de doble GPU.
  2. Valida el presupuesto de potencia. Asegura margen de PSU; evita cables de alimentación PCIe en cadena para consumos altos.
  3. Bloquea versiones. Fija driver/firmware; despliega actualizaciones como en producción.
  4. Instrumenta. Loggea dmesg, eventos AER, razones de throttling de GPU, temperaturas y utilización.
  5. Fija expectativas. Para gaming, estás optimizando para estabilidad y pacing, no para capturas de benchmark.

Preguntas frecuentes

1) ¿SLI/CrossFire funcionó alguna vez de verdad?

Sí—a veces. En títulos bien perfilados con DX11, pipelines amigables con AFR y dependencias temporales mínimas, el escalado podía ser fuerte. El problema es que «a veces» no es una estrategia de producto.

2) ¿Por qué la VRAM no se sumaba entre GPUs en los juegos?

Porque cada GPU necesita acceso local a texturas y geometría a máxima velocidad, y el multi‑GPU de consumo típicamente espeja recursos por tarjeta. Sin un modelo de memoria unificada, no puedes tratar dos pools de VRAM como uno sin pagar costos elevados de sincronización y transferencia.

3) ¿Qué es el microstutter, operativamente hablando?

Es varianza de latencia. Entregas frames en intervalos inconsistentes—ráfagas y huecos—por lo que el movimiento se ve desigual. Es la razón por la que el «FPS medio» es una métrica peligrosamente incompleta.

4) ¿Por qué DX12/Vulkan hicieron que el multi‑GPU fuera más raro en lugar de más común?

Lo hicieron explícito. Eso es arquitectónicamente honesto pero traslada el trabajo al equipo del motor: gestión de recursos, sincronización, pruebas a través de combinaciones de GPU y cobertura de QA. La mayoría de los estudios no quiso financiar eso para una base de usuarios pequeña.

5) ¿Pueden dos GPUs diferentes trabajar juntas para juegos ahora?

No en la forma antigua de «el controlador lo hace por ti». El multi‑adaptador explícito puede, en teoría, usar GPUs heterogéneas, pero el soporte en el mundo real es raro y generalmente especializado. Para juegos típicos: asume que no.

6) ¿Y NVLink—lo arregla?

NVLink ayuda en ciertos escenarios peer‑to‑peer de ancho de banda y es valioso en cómputo. No resuelve automáticamente el pacing de frames, las dependencias temporales o el problema económico de software. Los interconectores no arreglan la arquitectura.

7) Si ya tengo dos GPUs, ¿qué debería hacer?

Para juegos: usa una GPU y vende la otra, o repúrposeala para cómputo/codificación. Para cómputo: usa frameworks que soporten explícitamente multi‑GPU y mide el escalado con tamaños de lotes realistas y el overhead de sincronización.

8) ¿Qué métricas debería confiar al probar multi‑GPU?

Percentiles de tiempo de fotograma (como el 99.º), sensación de latencia de entrada (difícil de medir, fácil de notar), utilización por GPU, margen de VRAM y logs de estabilidad.
El FPS medio es una métrica de vanidad en este contexto.

9) ¿Está el multi‑GPU completamente muerto?

No de forma general—solo en el gaming en tiempo real de consumo como vía de aceleración por defecto. El multi‑GPU prospera donde la carga se puede particionar limpiamente: render offline, cómputo científico, ML y algunos pipelines de visualización profesional.

Próximos pasos que realmente puedes tomar

Si estás pensando en multi‑GPU para juegos en 2026, aquí va un consejo directo: no. Compra la mejor GPU única que puedas justificar, luego optimiza para tiempos de fotograma, margen de VRAM y una pila de controladores estable. Obtendrás un sistema que se comporta de forma predecible, que es lo que quieres cuando eres quien tiene que depurarlo.

Si debes ejecutar multi‑GPU—porque tu carga es cómputo, render offline o visualización especializada—trátalo como infraestructura de producción:
fija versiones, despliega por etapas, instrumenta todo y asume que la segunda GPU aumenta la superficie de fallo más que el rendimiento.

Pasos prácticos:

  • Cambia tu mentalidad de pruebas de «FPS medio» a percentiles de tiempo de fotograma y ejecuciones reproducibles.
  • Valida ancho de enlace PCIe, topología y estabilidad de potencia antes de tocar controladores.
  • Decide por adelantado si tu aplicación usa multi‑GPU explícito; si no, deja de invertir tiempo.
  • Mantén una baseline de controladores conocida y trata las actualizaciones como despliegues controlados.

Extrañezas de red en Docker Desktop: acceso LAN, puertos y soluciones DNS que realmente funcionan

Ejecutas docker run -p 8080:80, visitas localhost:8080 y funciona. Le pasas la URL a un compañero en la misma Wi‑Fi y… nada.
O tu contenedor puede hacer curl a Internet pero no alcanza el NAS en tu LAN. O el DNS cambia aleatoriamente cada vez que tu VPN se conecta.

La red de Docker Desktop no está “rota”. Simplemente no es el modelo de red del host Linux que crees estar usando.
Es una VM, NAT, un montón de adaptadores específicos de plataforma y un puñado de nombres especiales que existen más que nada para salvarnos la cordura.

El modelo mental: por qué Docker Desktop es diferente

En Linux, Docker normalmente conecta los contenedores a una red puente en el host, usa iptables/nftables para NAT del tráfico saliente
y añade reglas DNAT para los puertos publicados. Tu host es el host. El kernel que ejecuta los contenedores es el mismo kernel que ejecuta tu shell.

Docker Desktop en macOS y Windows es diferente por diseño. Ejecuta una pequeña VM Linux (o un entorno Linux vía WSL2),
y los contenedores viven detrás de una frontera de virtualización. Esa frontera explica por qué la “red host” se comporta de forma extraña,
por qué el acceso LAN no es simétrico y por qué la publicación de puertos puede sentirse como orientada solo a localhost.

Piensa en capas:

  • Tu sistema operativo físico (macOS/Windows): tiene tu interfaz Wi‑Fi/Ethernet, tu cliente VPN y tu firewall.
  • La VM de Docker / WSL2: tiene su propia NIC virtual, su propia tabla de enrutamiento, sus propios iptables y su propio comportamiento de DNS.
  • Redes de contenedores: puentes dentro de ese entorno Linux; tus contenedores raramente tocan la LAN física directamente.
  • Adaptador de publicación de puertos: Docker Desktop reenvía puertos desde el OS host a la VM y luego al contenedor.

Así que cuando alguien dice “el contenedor no puede alcanzar la LAN”, tu primera respuesta debería ser: “¿Qué capa no puede alcanzar a qué capa?”

Hechos interesantes y breve historia (lo que explica el dolor actual)

  1. El modelo de red original de Docker asumía Linux. Docker popularizó el patrón “puente + NAT + iptables” porque Linux lo hacía fácil y portátil.
  2. macOS no puede ejecutar contenedores Linux de forma nativa. Docker Desktop en macOS siempre ha dependido de una VM Linux porque los contenedores necesitan funciones del kernel Linux (namespaces, cgroups).
  3. Windows tuvo dos épocas. Primero llegó Docker Desktop basado en Hyper‑V; luego WSL2 se convirtió en la ruta predeterminada para mejor comportamiento de sistema de archivos y recursos, con peculiaridades de red diferentes.
  4. host.docker.internal existe porque “el host” es ambiguo. Dentro de un contenedor, “localhost” es el contenedor; Docker Desktop necesitó un nombre estable para “el sistema host”.
  5. Los puertos publicados no son solo reglas iptables en Desktop. En Linux lo son; en Desktop a menudo se implementan mediante un proxy/reenviador en espacio de usuario a través de la frontera VM.
  6. Los clientes VPN adoran reescribir tu DNS y rutas. Con frecuencia instalan un nuevo servidor DNS, bloquean DNS dividido o añaden una interfaz virtual con prioridad sobre el Wi‑Fi.
  7. La seguridad de endpoints corporativos con frecuencia inyecta un proxy local. Esto puede romper el DNS de contenedores, MITM TLS o desviar silenciosamente el tráfico hacia infraestructura de inspección.
  8. ICMP te puede engañar en redes virtuales. “No se puede hacer ping” no significa de forma fiable “no se puede conectar”, especialmente cuando los firewalls bloquean ICMP pero permiten TCP.

Broma #1: la red de Docker Desktop es como un organigrama: siempre hay una capa más de la que crees, y nunca es la capa responsable.

Guía de diagnóstico rápido (comprueba primero/segundo/tercero)

La forma más rápida de ganar es dejar de adivinar. Diagnostica en este orden, porque aisla capas con el menor esfuerzo.

1) ¿Es un problema de publicación de puertos o de enrutamiento/DNS?

  • Si localhost:PORT funciona en tu máquina pero clientes de la LAN no pueden alcanzarlo, probablemente sea firewall del host/dirección de enlace/filtrado de rutas por VPN.
  • Si los contenedores no pueden resolver nombres o alcanzar cualquier host externo, empieza por DNS y el enrutamiento saliente dentro del contenedor/VM.

2) Identifica dónde muere el paquete (host OS → VM → contenedor)

  • Desde el OS host: ¿puedes alcanzar el objetivo de la LAN?
  • Desde dentro de un contenedor: ¿puedes alcanzar el mismo objetivo de la LAN por IP?
  • Desde dentro de un contenedor: ¿puedes resolver el nombre?

3) Verifica la dirección de enlace/escucha real y el reenviador

  • ¿El servicio escucha en 0.0.0.0 dentro del contenedor, o solo en 127.0.0.1?
  • ¿Docker publica el puerto en todas las interfaces o solo en localhost?
  • ¿El firewall del host está bloqueando entradas desde la LAN?

4) Comprueba VPN y comportamiento de anulación de DNS temprano

  • Si el problema aparece/desaparece con la VPN, deja de tratarlo como un bug de Docker. Es política, rutas, DNS o inspección.

5) Solo entonces modifica la configuración de Docker Desktop

  • Cambiar servidores DNS o rangos de red puede ayudar, pero hazlo con evidencia. Si no, solo crearás un nuevo misterio.

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

Estas son las comprobaciones que realmente ejecuto. Cada una incluye: comando, salida de ejemplo, qué significa y qué decisión tomar después.
Los comandos se muestran con un prompt genérico; adapta los nombres de interfaces e IPs a tu entorno.

Task 1: Confirmar qué contexto de Docker estás usando

cr0x@server:~$ docker context ls
NAME                DESCRIPTION                               DOCKER ENDPOINT
default *           Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
desktop-linux       Docker Desktop                            unix:///Users/me/.docker/run/docker.sock

Significado: Si crees que estás hablando con Desktop pero estás en un daemon remoto (o viceversa), todas las suposiciones de red serán incorrectas.
Decisión: Si el contexto marcado con * no es el que esperas, cámbialo: docker context use desktop-linux.

Task 2: Inspeccionar la IP de un contenedor y su adjunción de red

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Ports}}'
NAMES          PORTS
web            0.0.0.0:8080->80/tcp
db             5432/tcp
cr0x@server:~$ docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}} {{.Gateway}}{{end}}' web
/web 172.17.0.2 172.17.0.1

Significado: El contenedor vive en un puente interno (aquí 172.17.0.0/16). Eso no es tu LAN.
Decisión: Si intentas alcanzar 172.17.0.2 desde otro portátil en la Wi‑Fi, detente. Publica un puerto o usa un patrón de red diferente.

Task 3: Comprobar en qué dirección escucha realmente tu servicio

cr0x@server:~$ docker exec -it web sh -lc "ss -lntp | head -n 5"
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      4096   0.0.0.0:80         0.0.0.0:*     users:(("nginx",pid=1,fd=6))

Significado: Escuchar en 0.0.0.0 es bueno; acepta tráfico desde la red del contenedor.
Si ves 127.0.0.1:80, la publicación de puertos “funcionará” de formas confusas o fallará por completo.
Decisión: Si está enlazado a localhost, corrige la configuración de la app: enlaza a 0.0.0.0.

Task 4: Verificar los enlaces de puertos publicados en el lado de Docker

cr0x@server:~$ docker port web
80/tcp -> 0.0.0.0:8080

Significado: Docker cree que publicó en todas las interfaces.
Decisión: Si muestra 127.0.0.1:8080, los clientes LAN no lo alcanzarán. Re‑ejecuta con -p 0.0.0.0:8080:80 (o corrige tu archivo compose).

Task 5: Confirmar que el OS host está escuchando en el puerto esperado

cr0x@server:~$ ss -lntp | grep ':8080'
LISTEN 0      4096      0.0.0.0:8080     0.0.0.0:*    users:(("com.docker.backend",pid=2314,fd=123))

Significado: En Desktop, a menudo ves el proceso backend de Docker escuchando, no el PID del contenedor. Eso es normal.
Decisión: Si nada está escuchando, tu publicación no se aplicó o otro proceso se llevó el puerto.

Task 6: Probar desde el OS host para confirmar que el camino de reenvío funciona

cr0x@server:~$ curl -sS -D- http://127.0.0.1:8080/ | head
HTTP/1.1 200 OK
Server: nginx/1.25.3
Date: Sat, 03 Jan 2026 09:12:52 GMT
Content-Type: text/html

Significado: El reenvío de puerto host→contenedor funciona localmente.
Decisión: Si los clientes LAN no pueden conectar, céntrate en firewall/VPN/enlace-a-localhost, no en la app del contenedor.

Task 7: Probar desde un par LAN (simúlalo con otro namespace/host si puedes)

cr0x@server:~$ nc -vz 192.168.1.50 8080
Connection to 192.168.1.50 8080 port [tcp/http-alt] succeeded!

Significado: El puerto es alcanzable desde la LAN.
Decisión: Si falla con “timed out” probablemente tengas problemas de firewall/enrutamiento. Si “refused”, algo está escuchando pero no acepta en esa interfaz o el reenviador no está enlazado correctamente.

Task 8: Comprobar la configuración DNS del contenedor

cr0x@server:~$ docker exec -it web sh -lc "cat /etc/resolv.conf"
nameserver 192.168.65.5
search localdomain
options ndots:0

Significado: Docker Desktop con frecuencia inyecta una IP de resolvedor stub (ejemplo: 192.168.65.5) dentro de la red de la VM.
Decisión: Si ese nameserver es inalcanzable o se comporta mal (común con VPNs), anula el DNS a nivel de daemon/compose.

Task 9: Probar resolución DNS dentro del contenedor (no adivines)

cr0x@server:~$ docker exec -it web sh -lc "getent hosts example.com | head -n 2"
2606:2800:220:1:248:1893:25c8:1946 example.com
93.184.216.34 example.com

Significado: El DNS funciona lo suficiente como para resolver AAAA y A.
Decisión: Si se cuelga o no devuelve nada, tienes un problema en la ruta DNS. Siguiente paso: intenta resolver usando un servidor específico (si tienes herramientas instaladas) o anula los resolvedores.

Task 10: Probar conectividad por IP directa a un recurso LAN desde dentro del contenedor

cr0x@server:~$ docker exec -it web sh -lc "nc -vz 192.168.1.10 445"
192.168.1.10 (192.168.1.10:445) open

Significado: El enrutamiento contenedor → VM → host OS → LAN funciona para ese destino.
Decisión: Si la IP funciona pero el nombre falla, es DNS. Si ninguno funciona, es enrutamiento/VPN/política.

Task 11: Comprobar la ruta por defecto del contenedor (básico pero decisivo)

cr0x@server:~$ docker exec -it web sh -lc "ip route"
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link  src 172.17.0.2

Significado: El contenedor enruta hacia la puerta de enlace del puente. La puerta de enlace decide luego cómo alcanzar tu LAN/Internet.
Decisión: Si falta la ruta por defecto o es incorrecta, has creado una configuración de red personalizada; vuelve atrás y prueba con una red de puente estándar.

Task 12: Comprobar si estás colisionando con una subred corporativa/VPN

cr0x@server:~$ ip route | head -n 12
default via 192.168.1.1 dev wlan0
10.0.0.0/8 via 10.8.0.1 dev tun0
172.16.0.0/12 via 10.8.0.1 dev tun0
192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.50

Significado: Si tus redes Docker usan 172.16.0.0/12 y tu VPN también enruta 172.16.0.0/12, has creado un enrutamiento ambiguo.
Desktop es especialmente sensible al solapamiento porque ya está haciendo NAT.
Decisión: Cambia los rangos de subred internos de Docker para evitar solapamientos con rutas corporativas.

Task 13: Inspeccionar redes Docker y sus subredes

cr0x@server:~$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a1b2c3d4e5f6   bridge    bridge    local
f1e2d3c4b5a6   host      host      local
123456789abc   none      null      local
cr0x@server:~$ docker network inspect bridge --format '{{(index .IPAM.Config 0).Subnet}}'
172.17.0.0/16

Significado: Ahora sabes qué subredes está consumiendo Docker.
Decisión: Si esto se solapa con rutas VPN o tu LAN, muévela.

Task 14: Validar que el contenedor puede alcanzar el OS host mediante el nombre especial de Docker Desktop

cr0x@server:~$ docker exec -it web sh -lc "getent hosts host.docker.internal"
192.168.65.2    host.docker.internal

Significado: El mapeo especial existe y apunta al endpoint del host que Docker proporciona.
Decisión: Si este nombre no resuelve, estás en una configuración antigua, un modo de red personalizado o algo alteró el DNS dentro del contenedor. Usa IPs explícitas solo como último recurso.

Patrones de acceso LAN: qué funciona y qué es engañoso

Hay tres solicitudes comunes:

  • LAN → tu servicio en contenedor (un compañero quiere acceder a tu servidor de desarrollo).
  • Contenedor → recurso LAN (el contenedor necesita acceder a un NAS, impresora, API interna, Kerberos, lo que sea).
  • Contenedor → OS host (el contenedor llama a un servicio que se ejecuta en tu portátil).

Patrón A: LAN → contenedor vía puertos publicados (el único predeterminado sensato)

Publica puertos en el OS host, no intentes repartir IPs de contenedor.
Con Docker Desktop no puedes tratar las IPs de contenedor como enrutable en la LAN física. Viven detrás de NAT, dentro de una VM, y detrás de otro NAT si tu OS también está haciendo algo inteligente.

Qué hacer:

  • Enlaza a todas las interfaces: -p 0.0.0.0:8080:80 o en Compose "8080:80" y asegúrate de que no se publique por defecto solo en localhost.
  • Abrir el firewall del host para ese puerto (y limitar el alcance; no expongas tu base de datos de desarrollo al Wi‑Fi de Starbucks).
  • Si tu VPN prohíbe entradas desde la LAN mientras está conectada, acepta la realidad: prueba sin VPN o usa un entorno de desarrollo apropiado en otro lugar.

Patrón B: contenedor → recursos LAN (el enrutamiento funciona hasta que deja de hacerlo)

Los contenedores alcanzan tu LAN generalmente por defecto, porque Docker Desktop hace NAT del tráfico saliente a través del OS host.
Luego conectas una VPN y el OS host cambia DNS y rutas. De repente tu contenedor no puede resolver o no puede alcanzar subredes que ahora están “propiedad” de la VPN.

Cuando falla, falla de algunas maneras repetibles:

  • Solapamiento de subred: Docker elige un rango privado que tu VPN enruta. Los paquetes desaparecen en el túnel.
  • Desajuste de DNS dividido: el host resuelve nombres internos vía DNS corporativo, pero los contenedores están atascados en un resolvedor stub que no reenvía dominios divididos correctamente.
  • Política de firewall: el endpoint corporativo niega tráfico desde interfaces virtuales “desconocidas”.

Patrón C: contenedor → servicios del OS host (usa los nombres especiales)

Usa host.docker.internal. Para eso sirve.
No es elegante, pero es estable frente a cambios de DHCP y menos frágil que codificar 192.168.x.y.

Si estás en Linux (no Desktop) puede que no lo tengas; en Desktop generalmente sí.

Puertos: publicación, direcciones de enlace y por qué los compañeros no pueden alcanzar tu servidor de desarrollo

Los puertos publicados son la moneda de “hacer que mi contenedor sea alcanzable”. Todo lo demás es deuda.

Localhost no es una virtud moral, es una dirección de enlace

Dos cosas diferentes se confunden constantemente:

  • Dónde escucha la app dentro del contenedor (127.0.0.1 vs 0.0.0.0).
  • Dónde Docker enlaza el puerto publicado en el host (127.0.0.1:PORT vs 0.0.0.0:PORT).

Si cualquiera de los dos es “solo localhost”, los clientes LAN pierden. Y perderás tiempo culpando a la otra capa.

Consejo para Compose: no te enlaces accidentalmente a localhost

Compose admite enlace explícito por IP del host. Esto es genial cuando lo deseas y horrible cuando no.

cr0x@server:~$ cat docker-compose.yml
services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"

Significado: Ese servicio es intencionalmente accesible solo desde el OS host.
Decisión: Si quieres acceso LAN, cámbialo a "8080:80" o "0.0.0.0:8080:80", y luego maneja el alcance del firewall apropiadamente.

Cuando los puertos publicados aún no son alcanzables desde la LAN

Si Docker muestra 0.0.0.0:8080 pero los clientes LAN no pueden conectar:

  • Firewall del host: Firewall de aplicaciones en macOS, Windows Defender Firewall, herramientas de endpoint de terceros.
  • Selección de interfaz: el puerto puede estar enlazado, pero el OS puede bloquear entradas por Wi‑Fi mientras permite por Ethernet (o viceversa).
  • Política de VPN: algunos clientes aplican “bloquear LAN local” para reducir riesgo de movimiento lateral.
  • Quirks de hairpin NAT: algunas redes no te permiten alcanzar tu propia IP pública desde dentro; eso no es Docker, es tu router haciendo lo mejor que puede.

Broma #2: Nada mejora el trabajo en equipo como decirle a alguien “funciona en mi máquina” y querer decirlo como una declaración de arquitectura de red.

DNS: de “es inestable” a “es determinista”

El DNS es donde la extrañeza de Docker Desktop se convierte en folclore. El problema normalmente no es “Docker no puede hacer DNS”.
El problema es: ahora tienes al menos dos resolvedores (OS host y VM), a veces tres (el de la VPN), y no están de acuerdo sobre reglas de horizonte dividido.

Modo de fallo 1: el DNS del contenedor resuelve nombres públicos pero no los internos

Clásico DNS dividido corporativo: git.corp solo se resuelve vía servidores DNS internos, accesibles únicamente con la VPN.
Tu OS host hace lo correcto. Tu contenedor usa un resolvedor stub que no reenvía los dominios correctos a los servidores adecuados.

Opciones de solución, de mejor a peor:

  1. Configurar el DNS de Docker Desktop para usar tus resolvedores internos cuando estés en VPN, y resolvedores públicos cuando no. A veces es un conmutador manual porque “auto” puede ser poco fiable.
  2. DNS por proyecto en Compose:
    • Configura dns: con las IPs de resolvedores que puedan responder tanto nombres internos como externos (a menudo los proporcionados por la VPN).
  3. Codificar /etc/hosts dentro de contenedores. Esto es un parche táctico, no una estrategia.

Task 15: Anular DNS en Compose y verificar dentro del contenedor

cr0x@server:~$ cat docker-compose.yml
services:
  web:
    image: alpine:3.20
    command: ["sleep","infinity"]
    dns:
      - 10.8.0.53
      - 1.1.1.1
cr0x@server:~$ docker compose up -d
[+] Running 1/1
 ✔ Container web-1  Started
cr0x@server:~$ docker exec -it web-1 sh -lc "cat /etc/resolv.conf"
nameserver 10.8.0.53
nameserver 1.1.1.1

Significado: El contenedor ahora usa los servidores DNS que especificaste.
Decisión: Si los dominios internos ahora resuelven, has probado que es un problema de ruta DNS/DNS dividido, no un problema de la aplicación.

Modo de fallo 2: el DNS funciona, pero solo a veces (timeouts, builds lentos, instalaciones de paquetes inestables)

Fallos intermitentes de DNS suelen venir de:

  • Servidores DNS de VPN que sueltan UDP bajo carga o requieren TCP para respuestas grandes.
  • Agentes de seguridad corporativos que interceptan DNS y ocasionalmente tiemblan.
  • Problemas de MTU/MSS en enlaces tunelizados (DNS sobre UDP se fragmenta y luego muere silenciosamente).

Task 16: Detectar timeouts DNS vs NXDOMAIN dentro del contenedor

cr0x@server:~$ docker exec -it web-1 sh -lc "time getent hosts pypi.org >/dev/null; echo $?"
real    0m0.042s
user    0m0.000s
sys     0m0.003s
0

Significado: Éxito rápido.
Decisión: Si esto tarda segundos o falla de forma intermitente, prefiera cambiar resolvedores (o forzar TCP mediante un resolvedor diferente) en vez de reintentar indefinidamente en tus scripts de build.

Modo de fallo 3: el servicio interno funciona por IP pero no por nombre (y solo en VPN)

Eso es DNS dividido otra vez, pero con algo extra: a veces la VPN empuja un sufijo DNS y dominios de búsqueda al OS host,
pero el resolvedor de Docker Desktop no los hereda limpiamente.

Task 17: Confirmar dominios de búsqueda dentro del contenedor

cr0x@server:~$ docker exec -it web-1 sh -lc "cat /etc/resolv.conf"
nameserver 10.8.0.53
search corp.example
options ndots:0

Significado: El dominio de búsqueda está presente.
Decisión: Si falta, los FQDN pueden funcionar mientras que los nombres cortos fallan. Usa FQDNs o configura dominios de búsqueda a nivel de contenedor.

VPNs, túneles divididos y la “ayuda” de los endpoints corporativos

Las VPNs causan dos grandes clases de problemas: cambios de enrutamiento y cambios de DNS. Docker Desktop amplifica ambos porque es efectivamente una red anidada.

Enrutamiento: cuando la VPN se apropia de tu espacio RFC1918

Muchas redes corporativas enrutan grandes rangos privados como 10.0.0.0/8 o 172.16.0.0/12 a través del túnel.
Los valores predeterminados de Docker suelen usar 172.17.0.0/16 para el puente y otros rangos 172.x para redes definidas por el usuario.

En un host Linux puro, normalmente puedes gestionar esto con subredes de puente personalizadas e iptables. En Desktop aún puedes hacerlo, pero debes tratarlo como una configuración de primera clase.

Task 18: Crear una red definida por el usuario en una subred “segura”

cr0x@server:~$ docker network create --subnet 192.168.240.0/24 devnet
9f8c7b6a5d4e3c2b1a0f
cr0x@server:~$ docker run -d --name web2 --network devnet -p 8081:80 nginx:alpine
b1c2d3e4f5a6
cr0x@server:~$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web2
192.168.240.2

Significado: Has movido la red del contenedor lejos de rutas corporativas comunes.
Decisión: Si la alcanzabilidad relacionada con la VPN mejora, institucionaliza una política de subredes para redes de desarrollo.

Seguridad de endpoints: la middlebox invisible

Algunas herramientas de endpoint tratan las NICs virtuales como “no confiables”. Pueden bloquear entradas o salidas, o forzar el tráfico a través de un proxy.
Los síntomas incluyen: puertos publicados funcionan solo cuando se pausa el agente de seguridad, DNS se vuelve lento o servicios internos fallan TLS por inspección.

No puedes “SREar” tu camino fuera de la política. Lo que sí puedes hacer es obtener evidencia rápido y luego escalar con pruebas concretas.

Task 19: Demostrar que es firewall/política local con una prueba rápida de entrada

cr0x@server:~$ python3 -m http.server 18080 --bind 0.0.0.0
Serving HTTP on 0.0.0.0 port 18080 (http://0.0.0.0:18080/) ...

Significado: Esto no es Docker. Es un proceso host simple.
Decisión: Si un par LAN tampoco puede alcanzar esto, deja de depurar Docker y arregla la configuración del firewall/VPN de “bloquear red local”.

Específicos de Windows + WSL2 (a dónde van a morir los paquetes)

En Windows moderno, Docker Desktop a menudo ejecuta su engine dentro de WSL2. WSL2 tiene su propia red virtual (NAT detrás de Windows).
Eso significa que puedes tener: NAT del contenedor detrás de Linux, detrás del NAT de WSL2, detrás de reglas de firewall de Windows. Es NAT hasta el fondo.

Síntomas típicos en Windows

  • Puerto publicado alcanzable desde localhost de Windows pero no desde la LAN. Normalmente reglas de entrada de Windows Defender Firewall, o el enlace es solo loopback.
  • Los contenedores no pueden alcanzar una subred LAN que Windows sí alcanza. Normalmente las rutas de la VPN no se propagan como piensas a WSL2, o la política bloquea interfaces de WSL.
  • El DNS difiere entre Windows y WSL2. WSL2 escribe su propio /etc/resolv.conf; a veces apunta a un resolvedor del lado Windows que no puede ver el DNS de la VPN.

Task 20: Comprobar resolv.conf y tabla de rutas de WSL2 (desde dentro de WSL)

cr0x@server:~$ cat /etc/resolv.conf
nameserver 172.29.96.1
cr0x@server:~$ ip route | head
default via 172.29.96.1 dev eth0
172.29.96.0/20 dev eth0 proto kernel scope link src 172.29.96.100

Significado: WSL2 está usando una puerta de enlace/resolvedor virtual del lado Windows.
Decisión: Si el DNS falla solo con la VPN, considera configurar el comportamiento DNS de WSL2 (resolv.conf estático) y alinear el DNS de Docker con los resolvedores de la VPN.

Específicos de macOS (pf, vmnet y la ilusión de localhost)

En macOS, Docker Desktop ejecuta una VM Linux y reenvía puertos de vuelta a macOS.
Tus contenedores no son ciudadanos de primera clase en tu LAN física. Son invitados detrás de un conserje muy educado.

En qué tropiezan los usuarios de macOS

  • “Funciona en localhost pero no desde mi teléfono.” Normalmente firewall de macOS o puerto publicado solo a loopback.
  • El DNS cambia cuando la Wi‑Fi cambia de red. El resolvedor del host cambia rápidamente; la VM a veces se queda atrás o cachea rarezas.
  • La VPN corporativa bloquea el acceso a la subred local. Tu teléfono no puede alcanzar tu portátil mientras la VPN está conectada, independientemente de Docker.

Task 21: Confirmar que el OS host tiene la IP y la interfaz correctas para pruebas LAN

cr0x@server:~$ ip addr show | sed -n '1,25p'
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 192.168.1.50/24 brd 192.168.1.255 scope global dynamic wlan0

Significado: Tu IP de LAN es 192.168.1.50.
Decisión: Esta es la dirección que un par LAN debe usar para alcanzar tu puerto publicado. Si los pares usan una IP antigua, están probando la máquina equivocada.

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

1) Síntoma: localhost:8080 funciona, el compañero no puede alcanzar 192.168.x.y:8080

  • Causa raíz: Puerto publicado solo en 127.0.0.1, o firewall del host bloquea entrada.
  • Solución: Publica en todas las interfaces (-p 0.0.0.0:8080:80), luego permite entrada para ese puerto en el firewall del host para el perfil de red correcto.

2) Síntoma: el contenedor puede alcanzar Internet pero no 192.168.1.10 (NAS LAN)

  • Causa raíz: Política de VPN “bloquear LAN local” o rutas que empujan subredes LAN dentro del túnel.
  • Solución: Prueba con la VPN desconectada; si eso lo arregla, solicita excepciones de túnel dividido o ejecuta la carga de trabajo en un entorno adecuado (VM remota, staging). No pelees con la política mediante trucos.

3) Síntoma: el contenedor puede alcanzar IPs LAN pero fallan nombres internos

  • Causa raíz: DNS dividido no propagado a Docker Desktop; contenedores usan un stub que no ve las zonas internas.
  • Solución: Configura DNS por proyecto (dns: en Compose) para incluir servidores DNS corporativos accesibles con la VPN; verifica con getent hosts.

4) Síntoma: DNS falla durante builds (apt/npm/pip falla aleatoriamente)

  • Causa raíz: DNS UDP poco fiable a través de la VPN, problemas de MTU, interceptación de endpoint.
  • Solución: Prefiere resolvedores estables; usa dos resolvedores (interno + público) donde la política lo permita; reduce el riesgo de fragmentación abordando MTU en la capa VPN si la controlas.

5) Síntoma: el servicio está publicado, pero obtienes “connection refused” desde la LAN

  • Causa raíz: La app escucha solo en localhost del contenedor, o se publicó el puerto equivocado.
  • Solución: Comprueba ss -lntp dentro del contenedor; corrige la dirección de enlace; verifica docker port y el mapeo de puertos del contenedor.

6) Síntoma: no puedo conectar a host.docker.internal desde el contenedor

  • Causa raíz: Se anuló el DNS que provee el nombre especial, o estás usando un modo de red donde Desktop no lo inyecta.
  • Solución: Evita anular DNS a ciegas; si debes hacerlo, asegúrate de que el nombre especial siga resolviendo (o añade una entrada explícita vía extra_hosts como último recurso).

7) Síntoma: todo falla solo en una red Wi‑Fi

  • Causa raíz: Esa red aísla clientes (AP isolation) o bloquea conexiones entrantes entre dispositivos.
  • Solución: Usa una red adecuada (o cableada), o ejecuta el servicio detrás de un túnel inverso; no asumas que “misma Wi‑Fi” significa “accesible mutuamente”.

Tres mini-historias corporativas (realistas, anonimizadas, dolorosas)

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

Un equipo de producto montó un entorno demo en portátiles para un taller con clientes en sitio. El plan era simple: ejecutar unos servicios en Docker Desktop, publicar puertos
y que los asistentes se conectaran por la Wi‑Fi del hotel. Todos habían hecho “-p 8080:8080” mil veces.
La suposición equivocada fue pensar que Docker Desktop se comporta como un host Linux en una LAN plana.

La mañana del taller, la mitad de los asistentes no pudo conectar. Los servicios estaban arriba. Curl local funcionaba. Los presentadores a veces podían alcanzarse entre ellos.
La gente empezó a reiniciar como si fuera 1998. El problema de red no era Docker; era la Wi‑Fi del hotel haciendo aislamiento de clientes: los dispositivos podían alcanzar Internet pero no entre sí.

La segunda suposición equivocada llegó de inmediato: “Usemos IPs de contenedor y evitamos el mapeo de puertos.”
Intentaron repartir 172.17.x.x visibles dentro de la VM de Docker, que por supuesto no eran alcanzables desde otros portátiles.
Eso llevó a diez minutos de tonterías confiadas y un diagrama de pizarra muy lamentado.

La solución fue aburrida: crear un hotspot local en un teléfono que permitiera tráfico peer‑to‑peer,
y publicar explícitamente los puertos necesarios en 0.0.0.0 con una regla rápida de firewall. Los servicios funcionaron. La suposición sobre “misma red” fue el verdadero fallo.

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

Un equipo de plataforma quería builds de CI más rápidos en máquinas de desarrollador. Notaron búsquedas DNS frecuentes durante builds y decidieron “optimizar” forzando a los contenedores a usar un resolvedor DNS público.
En una prueba en cafetería se veía muy bien: resoluciones más rápidas, menos timeouts, bonitas gráficas.

Entonces el primer ingeniero intentó hacer un build mientras estaba en VPN. Los registros de paquetes internos solo eran accesibles vía DNS corporativo y rutas internas.
De repente, los builds fallaron con “host not found” aunque el OS host resolvía bien. La solución temporal fue “desconectar la VPN”,
lo cual es una gran manera de crear el siguiente incidente.

La situación empeoró porque algunos nombres internos resolvían públicamente a IPs placeholder (por razones de seguridad), así que “DNS exitoso” pero las conexiones iban a un agujero negro.
Depurar fue brutal: verías registros A, la app haría timeout y todos culpaban TLS, proxies y Docker en orden aleatorio.

La solución final fue dejar de optimizar DNS globalmente. Pasaron a DNS por proyecto:
resolvedores internos primero cuando están en VPN, resolvedores públicos solo cuando están fuera. También documentaron cómo probar resolución dentro de contenedores, porque “resuelve en mi host” no es un dato válido en una red anidada.

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

Un servicio sensible a la seguridad usaba Docker Desktop para pruebas de integración locales. Necesitaba llamar a una API interna y además aceptar webhooks entrantes de un harness de pruebas en otra máquina de la oficina.
El equipo tenía un hábito que respeto: antes de cambiar ajustes, capturaban evidencia “conocida buena”—rutas, configuración DNS, enlaces de puertos—cuando funcionaba.

Un lunes, todo se rompió tras una actualización del OS. Los contenedores no podían resolver nombres internos. Los webhooks desde una máquina LAN dejaron de llegar.
En vez de adivinar, compararon el estado actual con la línea base: los puertos publicados ahora estaban ligados solo a localhost, y el stub DNS dentro de los contenedores apuntaba a una nueva IP del lado de la VM que no reenviaba DNS dividido.

Arreglaron el enlace de puertos en Compose, luego fijaron el DNS de los contenedores a los resolvedores internos mientras estaban en VPN.
Porque tenían la línea base, pudieron mostrar al equipo de seguridad exactamente qué cambió y por qué.
El incidente no se convirtió en una semana de culpas cruzadas.

Esa práctica—capturar la línea base, comparar cuando se rompe—es tan emocionante como ver pintura secar.
También funciona.

Listas de verificación / plan paso a paso (aburrido a propósito)

Lista 1: Exponer un servicio de Docker Desktop a tu LAN de forma fiable

  1. Asegúrate de que la app escucha en 0.0.0.0 dentro del contenedor (ss -lntp).
  2. Publica el puerto en todas las interfaces: -p 0.0.0.0:8080:80 (o Compose "8080:80").
  3. Confirma que Docker ve el mapeo: docker port CONTAINER.
  4. Confirma que el OS host está escuchando en ese puerto: ss -lntp | grep :8080.
  5. Prueba localmente: curl http://127.0.0.1:8080.
  6. Prueba desde un par LAN: nc -vz HOST_LAN_IP 8080.
  7. Si la prueba LAN falla, ejecuta un listener no Docker (python3 -m http.server) para aislar firewall/VPN de problemas de Docker.

Lista 2: Hacer que los contenedores alcancen recursos LAN internos (NAS, APIs internas)

  1. Desde el OS host, verifica que el objetivo sea alcanzable por IP.
  2. Desde dentro del contenedor, prueba conectividad por IP (nc -vz o curl).
  3. Si la IP falla solo con VPN, comprueba solapamiento de rutas (ip route) y políticas de la VPN (“bloquear LAN local”).
  4. Si la IP funciona pero el nombre falla, comprueba /etc/resolv.conf y resuelve con getent hosts.
  5. Anula DNS por proyecto usando dns: en Compose si es necesario.
  6. Evita solapamiento de subredes: mueve las redes Docker a un rango que tu VPN no enrute.

Lista 3: Estabilizar DNS para builds de desarrollo (pip/npm/apt dejan de fallar)

  1. Mide el tiempo de resolución dentro del contenedor con time getent hosts.
  2. Inspecciona los resolvedores actuales en /etc/resolv.conf.
  3. Si estás en VPN, prefiere los resolvedores internos proporcionados por la VPN (y añade un fallback público solo si está permitido).
  4. No codifiques un DNS público globalmente en todos los proyectos; romperás flujos de trabajo con DNS dividido.
  5. Vuelve a probar dentro del contenedor después de los cambios; no confíes en los resultados del OS host.

Preguntas frecuentes

1) ¿Por qué no puedo usar la IP del contenedor desde otra máquina en mi LAN?

Porque en Docker Desktop esa IP está en un puente interno dentro de una VM Linux (o entorno WSL2). Tu LAN no enruta hacia ella. Publica puertos en su lugar.

2) ¿Por qué -p 8080:80 funciona localmente pero no desde mi teléfono?

Usualmente o el puerto está ligado solo a localhost (explícita o implícitamente via Compose), o tu firewall/VPN del host bloquea conexiones entrantes desde la LAN.

3) ¿Cuál es la diferencia entre 127.0.0.1 y 0.0.0.0 en este contexto?

127.0.0.1 significa “aceptar conexiones solo desde esta misma pila de red”. 0.0.0.0 significa “escuchar en todas las interfaces”.
Necesitas 0.0.0.0 si esperas que otros dispositivos se conecten.

4) ¿Es --network host la solución para la red de Docker Desktop?

No. En Docker Desktop, “host network” no es lo mismo que la red host de Linux y a menudo no te dará lo que quieres. Por defecto usa bridge + puertos publicados.

5) ¿Por qué el DNS funciona en mi host pero no dentro de los contenedores?

El contenedor puede estar usando una ruta de resolvedor diferente (un stub dentro de la VM) y puede que no herede la configuración de DNS dividido de tu VPN. Verifica con cat /etc/resolv.conf y getent hosts dentro del contenedor, luego anula DNS por proyecto si es necesario.

6) ¿Debería configurar el DNS de Docker Desktop a un resolvedor público para “arreglarlo todo”?

Solo si nunca necesitas DNS interno. Los resolvedores públicos pueden romper dominios corporativos, registros de paquetes internos y configuraciones de horizonte dividido. Usa DNS específico por proyecto o comportamiento condicional ligado al estado de la VPN.

7) Mi contenedor no puede alcanzar un dispositivo LAN solo cuando la VPN está conectada. ¿Es culpa de Docker?

Casi nunca. Los clientes VPN pueden enrutar subredes privadas por el túnel o bloquear el acceso a la LAN local. Demuéstralo probando la misma conexión desde el OS host y desconectando la VPN como control.

8) ¿Cuál es la forma más fiable para que un contenedor llame a un servicio en mi portátil?

Usa host.docker.internal y mantenlo consistente entre entornos. Evita IPs de host codificadas que cambian con las redes Wi‑Fi.

9) ¿Cómo sé si el problema es firewall vs mapeo de puertos de Docker?

Ejecuta un listener no Docker en el host (como python3 -m http.server). Si la LAN no puede alcanzar eso, Docker no es el problema.

10) ¿Cuál es un buen principio para la cordura en redes Desktop?

Trata Docker Desktop como “contenedores detrás de una VM detrás de tu OS”. Publica puertos, evita solapamiento de subredes y valida DNS desde dentro del contenedor.

Conclusión: siguientes pasos que puedes hacer hoy

La red de Docker Desktop deja de ser extraña cuando dejas de esperar que sea la red host de Linux. Es una frontera de VM con una capa de reenvío.
Una vez que aceptes eso, la mayoría de los problemas se reducen a tres cubos: direcciones de enlace, firewall/política de VPN y deriva del DNS/resolvedores.

Pasos prácticos:

  1. Escoge un servicio de prueba, publícalo en 0.0.0.0 y verifica la alcanzabilidad LAN de extremo a extremo usando ss, curl y nc.
  2. Captura una línea base cuando las cosas funcionen: docker port, /etc/resolv.conf del contenedor y la tabla de enrutamiento del host.
  3. Si usas VPN, evita que las redes Docker se solapen con rutas corporativas. Estandariza un rango de subred “seguro” para redes de desarrollo.
  4. Haz del DNS una configuración por proyecto cuando los nombres internos importen. Las “soluciones” globales son la forma de crear rupturas entre equipos.

Una idea parafraseada de Werner Vogels (CTO de Amazon): “Todo falla; diseña tus sistemas—y tus operaciones—para absorber esa falla.”
La red de Docker Desktop no es especial. Es simplemente fallo con capas extra.

Ubuntu 24.04: «No se pudo obtener la conexión D-Bus» — arreglar sesiones y servicios rotos (caso #48)

Ejecutas systemctl y aparece: «No se pudo obtener la conexión D-Bus». De pronto tu “reinicio simple” se convierte en una escena del crimen: los servicios no se comunican, los inicios de sesión parecen embrujados y toda automatización que espera una sesión limpia empieza a fallar.

Este error rara vez es “solo D-Bus”. Suele ser un contrato roto entre systemd, tu sesión/inicio de sesión y los sockets del bus bajo /run. La solución es aburrida—pero solo después de que dejes de adivinar y empieces a probar.

Qué significa realmente el error (y qué no significa)

Cuando una herramienta dice «No se pudo obtener la conexión D-Bus», se queja de que no puede alcanzar un socket de bus de mensajes que espera que exista. En Ubuntu 24.04, los llamantes habituales son systemctl, loginctl, componentes de GNOME, avisos de policykit, asistentes de snapd, o cualquier proceso que espere ya sea:

  • El bus del sistema en /run/dbus/system_bus_socket (usado para servicios a nivel del sistema), o
  • El bus de la sesión de usuario (por usuario) típicamente en /run/user/UID/bus, gestionado por systemd --user y dbus-daemon o dbus-broker según la configuración.

La frase es engañosa porque la causa raíz a menudo no es “D-Bus está caído”. El bus puede estar bien; tu entorno puede ser incorrecto, tu directorio runtime puede no existir, podrías estar dentro de un contenedor/namespace, o podrías estar usando sudo de una manera que elimina las variables del bus.

Dos reglas que te mantienen cuerdo:

  1. Decide si necesitas el bus del sistema o el bus de usuario. Si administras servicios con systemctl (ámbito del sistema), te importa PID 1, dbus y el socket del sistema. Si ejecutas acciones de escritorio/sesión, te importan systemd --user, XDG_RUNTIME_DIR y el socket por usuario.
  2. Siempre prueba el socket, no tus corazonadas. La mayoría de las caídas de “conexión D-Bus” son en realidad rutas faltantes bajo /run, sesiones de usuario muertas o un gestor de inicio de sesión roto.

Una idea parafraseada de Gene Kim (autor sobre DevOps/fiabilidad): La mejora viene de reducir el trabajo en curso y hacer visibles los problemas temprano. Eso aplica aquí: haz visible la falla comprobando primero las rutas del bus y el estado de la sesión, no reiniciando demonios al azar.

Guion de diagnóstico rápido

Cuando esto ocurre en producción a las 02:00, no quieres teoría. Quieres un bucle de triaje que converja.

Paso 1: Identificar qué bus falla

  • Si el error aparece al ejecutar systemctl status foo como root, probablemente sea el bus del sistema o la conectividad con PID 1.
  • Si el error aparece en una app de escritorio, ajustes de GNOME, o systemctl --user, es el bus de la sesión de usuario (/run/user/UID/bus).
  • Si solo sucede por SSH o automatización, sospecha de variables de entorno y shells no interactivos.

Paso 2: Comprobar sockets y directorios runtime (señal más rápida)

  • ¿Existe /run/dbus/system_bus_socket y es un socket?
  • ¿Existe /run/user/UID y es propiedad del usuario?
  • ¿Existe /run/user/UID/bus y es un socket?

Paso 3: Validar el gestor de sesión y el estado de systemd

  • systemctl is-system-running te dice si PID 1 está saludable.
  • systemctl status dbus te indica si el servicio del bus del sistema existe/arrancó.
  • loginctl list-sessions te dice si logind ve tu sesión (crítico para la creación de /run/user/UID).

Paso 4: Arregla la capa correcta, no la más ruidosa

  • ¿Falta /run/user/UID? Arregla el ciclo de vida de logind/sesión.
  • ¿El socket existe pero acceso denegado? Arregla permisos, políticas SELinux/AppArmor o el contexto del usuario.
  • ¿Funciona localmente pero no con sudo? Arregla la preservación del entorno; no “reinicies dbus” por despecho.

Datos interesantes y contexto (depurarás más rápido)

  • D-Bus fue diseñado a principios de los 2000 para reemplazar mecanismos ad-hoc de IPC en escritorios Linux; más tarde se volvió estándar también para servicios del sistema.
  • systemd no creó D-Bus, pero systemd hizo más explícitos los patrones de dependencia de D-Bus con ordenación de unidades, activación por socket y servicios de usuario.
  • Los directorios runtime de usuario bajo /run/user/UID suelen ser creados por systemd-logind cuando se inicia una sesión—y se eliminan cuando termina la última sesión.
  • Ubuntu ha incluido tanto dbus-daemon como alternativas (como dbus-broker en algunos entornos); lo que importa es el contrato del socket, no la marca de implementación.
  • XDG_RUNTIME_DIR es parte de la especificación XDG; debe ser específico del usuario, seguro y efímero—exactamente lo opuesto a un directorio aleatorio bajo /tmp.
  • systemctl habla con systemd sobre D-Bus; si systemctl no puede alcanzar un bus, no puede pedirle nada a systemd, incluso si systemd está técnicamente vivo.
  • Las sesiones SSH no siempre son “sesiones logind” dependiendo de la configuración de PAM; cuando no lo son, puedes perder la creación automática del directorio runtime y la disponibilidad del bus de usuario.
  • Los contenedores a menudo no tienen un bus del sistema completo porque PID 1 no es systemd, o porque /run está aislado. Este error es normal allí a menos que lo conectes deliberadamente.
  • PolicyKit (polkit) depende de D-Bus para consultas de autorización; el acceso roto al bus puede parecer “los avisos de autenticación no aparecen” o “permiso denegado” sin interfaz gráfica.

Broma #1: D-Bus es como el correo de oficina—cuando falla, todos de repente descubren cuántas cosas dependían de él sin haberlo sabido.

Guía de campo: aislar qué “bus” no alcanzas

Hay algunas formas comunes de fallo:

  • Root en un servidor: systemctl falla. Por lo general el socket del bus del sistema falta, la unidad dbus falló, o PID 1 está en un estado degradado/medio muerto.
  • Sesión de usuario de escritorio: fallan ajustes de GNOME, gsettings se rompe, systemctl --user falla. Suelen faltar XDG_RUNTIME_DIR, /run/user/UID o systemd --user no está en ejecución.
  • Automatización vía sudo: funciona como tu usuario, falla como root, o al revés. Normalmente las variables de entorno y el contexto de sesión son incorrectos.
  • Dentro de contenedores/CI: systemctl y busctl fallan por diseño porque no hay systemd PID 1 ni bus del sistema.

Clave: el bus es un archivo de socket Unix. Si el socket no está, no vas a “reintentar con más fuerza.” Si está pero tu proceso no puede acceder, tratas problemas de permisos, namespaces o identidad. Si está y es accesible pero las respuestas fallan, entonces hay un problema del demonio.

Tareas prácticas: comandos, salida esperada y decisiones

Estas son las tareas que realmente ejecuto. Cada una incluye qué significa la salida y qué decisión tomar a continuación. Ejecútalas en orden hasta que el modo de fallo sea obvio. No colecciones registros por diversión; estás reduciendo el espacio de búsqueda.

Task 1: Confirmar el comando que falla y el contexto exacto

cr0x@server:~$ whoami
cr0x
cr0x@server:~$ systemctl status ssh
Failed to get D-Bus connection: No such file or directory

Significado: El cliente no puede alcanzar su socket del bus. “No such file or directory” sugiere una ruta de socket faltante, no un problema de permisos.

Decisión: Determina si es una falla del bus del sistema (ámbito root/sistema) o del bus de usuario (ámbito usuario). Siguiente: comprueba si eres root y qué systemctl ejecutaste.

Task 2: Comprobar si PID 1 es systemd (contenedores y chroots)

cr0x@server:~$ ps -p 1 -o pid,comm,args
  PID COMMAND         COMMAND
    1 systemd         /sbin/init

Significado: PID 1 es systemd; systemctl debería funcionar si la ruta del bus del sistema está presente.

Decisión: Si PID 1 no es systemd (común en contenedores), la “solución” es evitar systemctl o ejecutar un init apropiado. Si es systemd, continúa.

Task 3: Verificar que exista el socket del bus del sistema

cr0x@server:~$ ls -l /run/dbus/system_bus_socket
srwxrwxrwx 1 root root 0 Dec 30 10:12 /run/dbus/system_bus_socket

Significado: El archivo socket del bus del sistema existe y es un socket (la primera letra s en permisos). Que sea escribible por todos es normal para el endpoint; el acceso se controla aún por la política de D-Bus.

Decisión: Si falta: enfócate en el servicio dbus y en problemas de arranque temprano. Si está presente: prueba si dbus responde.

Task 4: Comprobar la salud del servicio dbus (bus del sistema)

cr0x@server:~$ systemctl status dbus --no-pager
● dbus.service - D-Bus System Message Bus
     Loaded: loaded (/usr/lib/systemd/system/dbus.service; static)
     Active: active (running) since Mon 2025-12-30 10:12:01 UTC; 2min ago
TriggeredBy: ● dbus.socket
       Docs: man:dbus-daemon(1)
   Main PID: 842 (dbus-daemon)
      Tasks: 1 (limit: 18939)
     Memory: 3.8M
        CPU: 52ms

Significado: El bus del sistema está en ejecución; el problema puede ser la capacidad de systemctl para conectarse a systemd (no a dbus), o un problema de namespace/permiso.

Decisión: Si dbus está inactivo/falló, reinícialo y lee los registros. Si está activo, comprueba systemd mismo y el socket privado de systemd.

Task 5: Confirmar que systemd responde

cr0x@server:~$ systemctl is-system-running
running

Significado: PID 1 informa que está saludable. Si aún ves “Failed to get D-Bus connection”, puede que estés ejecutando systemctl en un entorno que no ve /run o que carece del namespace de montaje correcto.

Decisión: Si la salida es degraded o maintenance, ve directamente al journal por fallos sistémicos. Si es running pero los clientes fallan, sospecha de namespaces, chroot o problemas del sistema de archivos bajo /run.

Task 6: Inspeccionar el montaje de /run y el espacio libre (sí, en serio)

cr0x@server:~$ findmnt /run
TARGET SOURCE FSTYPE OPTIONS
/run   tmpfs  tmpfs  rw,nosuid,nodev,relatime,size=394680k,mode=755,inode64
cr0x@server:~$ df -h /run
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           386M  2.1M  384M   1% /run

Significado: /run es tmpfs; debe ser escribible y tener espacio/inodos. Si /run está en solo lectura o lleno, no se crearán sockets y obtendrás errores de bus faltante.

Decisión: Si está lleno/ro: arregla eso primero (a menudo un proceso descontrolado o un tamaño de tmpfs mal configurado). Si está sano: continúa con las comprobaciones de sesión de usuario si el error es de ámbito usuario.

Task 7: Determinar si tratas con el bus de usuario

cr0x@server:~$ echo "$XDG_RUNTIME_DIR"
/run/user/1000
cr0x@server:~$ echo "$DBUS_SESSION_BUS_ADDRESS"
unix:path=/run/user/1000/bus

Significado: Las variables de entorno apuntan al bus por usuario. Si alguna está vacía, tu sesión está incompleta (común con sudo, cron o PAM roto).

Decisión: Si están sin establecer: debes establecer un contexto de sesión correcto o configurar explícitamente un bus de usuario (prefiere lo primero). Si están establecidas: comprueba que el socket exista.

Task 8: Validar que el socket del bus de usuario exista y tenga propiedad sensata

cr0x@server:~$ id -u
1000
cr0x@server:~$ ls -ld /run/user/1000
drwx------ 12 cr0x cr0x 320 Dec 30 10:12 /run/user/1000
cr0x@server:~$ ls -l /run/user/1000/bus
srw-rw-rw- 1 cr0x cr0x 0 Dec 30 10:12 /run/user/1000/bus

Significado: El directorio runtime existe, es privado (0700) y el socket del bus existe. Bien. Si /run/user/1000 falta, tu sesión no se registró correctamente con logind.

Decisión: Si falta: salta a la resolución de loginctl y PAM/logind. Si está presente pero con propietario incorrecto: corrige la propiedad e investiga por qué se desvió (a menudo un script mal ejecutado como root).

Task 9: Demostrar que la instancia de systemd del usuario está viva

cr0x@server:~$ systemctl --user status --no-pager
● cr0x@server
    State: running
    Units: 221 loaded (incl. snap units)
     Jobs: 0 queued
   Failed: 0 units
    Since: Mon 2025-12-30 10:12:05 UTC; 2min ago
  

Significado: Tu gestor de usuario está en ejecución y accesible. Si obtienes “Failed to connect to bus”, la ruta del bus de usuario o el entorno están rotos.

Decisión: Si esto falla pero el socket existe, tu entorno puede estar mintiendo (XDG_RUNTIME_DIR incorrecto) o estás en un namespace distinto (común con sudo y algunas herramientas remotas).

Task 10: Usar loginctl para verificar que logind vea tu sesión

cr0x@server:~$ loginctl list-sessions
SESSION  UID USER SEAT  TTY
     21 1000 cr0x seat0 tty2

1 sessions listed.
cr0x@server:~$ loginctl show-user cr0x -p RuntimePath -p State -p Linger
RuntimePath=/run/user/1000
State=active
Linger=no

Significado: logind tiene una sesión activa para el usuario y sabe dónde está la ruta runtime. Si no hay sesiones, tu directorio runtime de usuario puede no crearse.

Decisión: Si la sesión falta por SSH: revisa la configuración de PAM y si tu ruta de inicio usa systemd/logind. Si necesitas servicios de usuario en segundo plano, considera lingering (con cuidado).

Task 11: Diagnosticar “sudo rompió mi bus” (clásico)

cr0x@server:~$ sudo -i
root@server:~# echo "$DBUS_SESSION_BUS_ADDRESS"

root@server:~# systemctl --user status
Failed to connect to bus: No medium found

Significado: La shell de root no tiene contexto del bus de usuario; systemctl --user bajo root no es tu sesión de usuario. Ese error es esperado.

Decisión: No “arregles” esto exportando variables aleatorias a root. Usa systemctl (ámbito del sistema) como root, y systemctl --user como el usuario dentro de la sesión. Si debes gestionar una unidad de usuario desde root, usa machinectl shell o runuser con el entorno adecuado, o apunta al gestor de usuario vía loginctl enable-linger y systemctl --user bajo ese usuario.

Task 12: Revisar el journal por la primera falla, no por la última quejarse

cr0x@server:~$ journalctl -b -u systemd-logind --no-pager | tail -n 20
Dec 30 10:11:58 server systemd-logind[701]: New session 21 of user cr0x.
Dec 30 10:11:58 server systemd-logind[701]: Watching system buttons on /dev/input/event3 (Power Button)
Dec 30 10:12:01 server systemd-logind[701]: Removed session 19.

Significado: logind está creando sesiones. Si en su lugar ves fallos repetidos al crear directorios runtime, ese es tu arma humeante.

Decisión: Si logind muestra errores sobre directorios runtime o cgroups, arregla esas capas. Reiniciar dbus no arreglará “no se puede crear /run/user/UID”.

Task 13: Confirmar que los paquetes dbus y el soporte de sesión de usuario estén instalados

cr0x@server:~$ dpkg -l | egrep 'dbus|dbus-user-session|libpam-systemd' | awk '{print $1,$2,$3}'
ii dbus 1.14.10-4ubuntu4.1
ii dbus-user-session 1.14.10-4ubuntu4.1
ii libpam-systemd 255.4-1ubuntu8

Significado: Los componentes requeridos existen. La ausencia de dbus-user-session puede llevar a comportamientos de bus de sesión faltantes en algunas instalaciones mínimas.

Decisión: Si faltan: instala los paquetes y vuelve a iniciar sesión. Si están presentes: pasa a PAM/logind y problemas de entorno.

Task 14: Comprobar los hooks de sesión PAM para systemd/logind (enfocado en SSH)

cr0x@server:~$ grep -R "pam_systemd.so" -n /etc/pam.d/sshd /etc/pam.d/login
/etc/pam.d/sshd:15:session    required     pam_systemd.so
/etc/pam.d/login:14:session    required     pam_systemd.so

Significado: PAM está configurado para registrar sesiones con systemd/logind para SSH y consolas. Si falta, puedes quedarte sin directorio runtime y sin bus de usuario.

Decisión: Si está ausente para la vía de inicio que usas: añádelo (con cuidado, control de cambios) y prueba con una nueva sesión. Si está presente: céntrate en por qué logind aún no crea directorios runtime (a menudo relacionado con lingering, problemas de cgroups o estado de systemd roto).

Task 15: Comprobar si el directorio runtime del usuario se elimina inesperadamente

cr0x@server:~$ sudo ls -l /run/user
total 0
drwx------ 12 cr0x cr0x 320 Dec 30 10:12 1000
drwx------ 10 gdm  gdm  280 Dec 30 10:11 120

Significado: Existen directorios runtime para usuarios activos. Si el tuyo desaparece al desconectar SSH, probablemente no tienes lingering y no hay sesión activa.

Decisión: Para servicios de usuario en segundo plano: considera loginctl enable-linger username. Para trabajo interactivo: asegúrate de tener una sesión real y evita ejecutar comandos dependientes de la sesión desde contextos sin sesión.

Task 16: Habilitar lingering (solo si realmente necesitas servicios de usuario sin inicio de sesión)

cr0x@server:~$ sudo loginctl enable-linger cr0x
cr0x@server:~$ loginctl show-user cr0x -p Linger
Linger=yes

Significado: El gestor de usuario puede sobrevivir más allá de los inicios de sesión, manteniendo servicios de usuario y el directorio runtime disponible.

Decisión: Usa esto para servicios sin cabeza ejecutados en ámbito de usuario (a veces agentes CI, podman por usuario, etc.). No lo habilites en todas partes “por si acaso.” Así es como se obtienen gestores de usuario zombis consumiendo RAM en hosts compartidos.

Task 17: Si systemctl falla como root, prueba D-Bus directamente

cr0x@server:~$ busctl --system list | head
NAME                      PID PROCESS         USER CONNECTION UNIT SESSION DESCRIPTION
:1.0                      842 dbus-daemon     root :1.0       -    -       -
org.freedesktop.DBus      842 dbus-daemon     root :1.0       -    -       -
org.freedesktop.login1    701 systemd-logind  root :1.2       -    -       -

Significado: El bus del sistema responde. Si systemctl aún falla, podrías tener un endpoint D-Bus de systemd roto o un desajuste en el entorno/namespace.

Decisión: Si busctl también falla: el bus del sistema está genuinamente roto. Si busctl funciona: céntrate en la conectividad de systemd y el entorno del cliente.

Task 18: Comprobar el socket privado de systemd (endpoint IPC de systemd)

cr0x@server:~$ ls -l /run/systemd/private
srw------- 1 root root 0 Dec 30 10:11 /run/systemd/private

Significado: El socket privado de systemd existe; systemctl lo usa en algunos caminos de código. Si falta, algo está muy mal con PID 1 o /run.

Decisión: Si falta: trátalo como un problema de systemd/sistema de archivos runtime; considera un reinicio controlado después de extraer logs. Si está presente: vuelve al alcance (sistema vs usuario) y problemas de namespace.

Task 19: Detectar problemas de chroot/namespace (común en shells de recuperación)

cr0x@server:~$ readlink /proc/$$/ns/mnt
mnt:[4026532585]
cr0x@server:~$ sudo readlink /proc/1/ns/mnt
mnt:[4026531840]

Significado: Tu shell está en un namespace de montaje distinto al de PID 1. Puede que no veas el verdadero /run donde viven los sockets.

Decisión: Si los namespaces difieren, ejecuta diagnósticos desde el namespace del host (o entra en él) en lugar de “arreglar” rutas fantasma en tu vista aislada.

Task 20: Último recurso, reinicios controlados (en el orden correcto)

cr0x@server:~$ sudo systemctl restart systemd-logind
cr0x@server:~$ sudo systemctl restart dbus
cr0x@server:~$ sudo systemctl daemon-reexec

Significado: Estos reinicios pueden recuperar un logind/dbus/systemd bloqueado. daemon-reexec es pesado; re-ejecuta PID 1 sin reiniciar.

Decisión: Haz esto solo después de confirmar que no estás en un contenedor y de haber capturado suficientes logs para explicar el incidente. Si las sesiones de usuario están rotas por logind, reiniciar logind puede desconectar sesiones; prográmalo como si realmente importara.

Errores comunes: síntoma → causa raíz → arreglo

1) «systemctl funciona como root localmente, falla por SSH»

Síntoma: Por SSH, systemctl devuelve “Failed to get D-Bus connection,” pero en la consola funciona.

Causa raíz: Estás en un entorno restringido (comando forzado, chroot, toolbox), o tu sesión SSH no ve /run del host (diferencia de namespace de montaje).

Arreglo: Confirma PID 1 y el namespace de montaje; asegúrate de que tu ruta SSH no esté chrooted y tenga acceso a /run. Usa Task 2 y Task 19.

2) «systemctl –user falla después de sudo -i»

Síntoma: Te conviertes en root e intentas gestionar servicios de usuario; falla con errores de bus.

Causa raíz: Root no tiene el entorno del bus de tu usuario. Además, el gestor de usuario de root no es el gestor de tu usuario.

Arreglo: Ejecuta systemctl --user como el usuario dentro de la sesión. Si debes hacerlo desde root, usa runuser -l username -c 'systemctl --user …' y asegúrate de una sesión adecuada (o habilita lingering).

3) «GNOME Settings no se abren; los avisos de polkit nunca aparecen»

Síntoma: Acciones de GUI fallan en silencio o se quejan de D-Bus.

Causa raíz: El bus de la sesión de usuario está roto: falta XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS está obsoleto, o falta /run/user/UID/bus.

Arreglo: Verifica Task 7/8. Cierra sesión y vuelve a entrar para recrear una sesión limpia. Si persiste, revisa logind e integración PAM.

4) «Trabajo cron falla con errores de D-Bus»

Síntoma: Un script que usa gsettings, notify-send o systemctl --user falla en cron.

Causa raíz: Cron se ejecuta sin una sesión de usuario y sin XDG_RUNTIME_DIR.

Arreglo: No ejecutes comandos de escritorio/sesión en cron a menos que crees un contexto de sesión. Usa servicios del sistema en su lugar, o habilita lingering y ejecuta un servicio de usuario que no dependa del estado de GUI.

5) «/run/user/UID existe pero es propiedad de root»

Síntoma: El directorio existe, pero los permisos son incorrectos; siguen errores del bus de usuario.

Causa raíz: Alguien ejecutó una “limpieza” como root y recreó directorios incorrectamente, o un script malicioso escribió en /run/user.

Arreglo: Cierra la sesión del usuario (termina sesiones), elimina el directorio runtime incorrecto y deja que logind lo recree. Si debes arreglar en caliente, corrige la propiedad y reinicia el gestor de usuario con cuidado.

6) «socket del bus del sistema faltante después del arranque»

Síntoma: /run/dbus/system_bus_socket está ausente; systemctl falla de forma amplia.

Causa raíz: dbus.socket o dbus.service no arrancó, o /run no se montó correctamente.

Arreglo: Valida el montaje de /run (Task 6), luego systemctl status dbus dbus.socket y revisa los logs del arranque temprano.

7) «Funciona en el host pero falla dentro de un contenedor»

Síntoma: systemctl y busctl fallan en una imagen de contenedor o runner CI.

Causa raíz: No hay systemd PID 1, no hay bus del sistema o /run está aislado.

Arreglo: No uses systemctl dentro de ese contenedor. Ejecuta el proceso del servicio en primer plano, o ejecuta un contenedor basado en systemd intencionalmente con los privilegios y montajes correctos.

Tres microhistorias corporativas desde el campo

Microhistoria #1: El incidente causado por una suposición equivocada

En una compañía mediana, un ingeniero on-call recibió una alerta de “host de despliegue no reinicia servicios.” Se conectó por SSH, ejecutó sudo systemctl restart app, y obtuvo “Failed to get D-Bus connection.” La suposición fue inmediata y segura: “dbus está caído; reinícialo.”

Reinició dbus. Luego logind. Luego intentó un daemon-reexec. El host se volvió más difícil de acceder y varias sesiones interactivas cayeron. La app seguía sin reiniciarse. El incidente se complicó.

El problema real fue mundano: el ingeniero no estaba en el host. Estaba en un chroot de mantenimiento que las herramientas de rescate del equipo usaban para trabajo de disco. Ese entorno tenía un namespace de montaje distinto y un /run distinto. Por supuesto que /run/dbus/system_bus_socket no existía allí; el socket del bus vivía en el namespace del host.

Una vez que salió del chroot y ejecutó el mismo comando en el entorno real del host, systemctl funcionó de inmediato. La “caída de D-Bus” fue un espejismo creado por el contexto. La solución fue añadir un banner claro en los entornos de rescate y enseñar al equipo a ejecutar Task 2 y Task 19 antes de tocar demonios.

Microhistoria #2: La optimización que salió mal

Otro equipo quiso tiempos de inicio más rápidos y menos procesos en segundo plano en estaciones de trabajo de desarrolladores. Alguien decidió “simplificar” quitando paquetes de la imagen base, incluidos componentes relacionados con la sesión que creían “fluff de escritorio”.

La imagen se distribuyó y fue rápida. Durante una semana. Luego llegaron los tickets: integraciones del IDE fallando, avisos de contraseña que no aparecían, toggles de configuración que no funcionaban, y uno curioso—servicios de usuario fallando solo después de reconectar por escritorio remoto.

Habían eliminado piezas que indirectamente garantizaban un bus de sesión de usuario estable. El bus del sistema aún existía, pero la infraestructura de sesión por usuario era inconsistente entre métodos de inicio. Algunos inicios creaban /run/user/UID correctamente; otros no, porque faltaban hooks de PAM y paquetes de sesión.

La optimización no fue “mala” por ahorrar CPU. Fue mala porque removió la andamiaje que hace predecible el bus de usuario. El rollback añadió los paquetes necesarios y estandarizó las rutas de inicio. El tiempo de inicio aumentó ligeramente y la tasa de incidentes cayó dramaticamente. A veces “rápido” es solo “frágil con mejor marketing.”

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

En un entorno regulado, un equipo tenía servidores Ubuntu que ocasionalmente necesitaban trabajo de consola en emergencias. Tenían una política que parecía anticuada: cada respuesta a incidentes empezaba capturando estado, incluyendo extractos de journalctl -b y una instantánea de las rutas de sockets en /run, antes de cualquier reinicio.

Parecía burocrático hasta que un host de producción empezó a lanzar errores de conexión D-Bus después de una actualización de kernel. El on-call siguió la política. Capturó findmnt /run, comprobó espacio libre, verificó que /run/systemd/private existiera y notó que /run/dbus/system_bus_socket faltaba. También capturó logs tempranos que mostraban advertencias del montaje tmpfs.

Porque tuvieron evidencia, no hicieron tonterías. Encontraron que /run se había montado en modo solo lectura debido a una sutil falla de initramfs/mount. Con eso corregido y un reinicio controlado, el socket del bus apareció, systemctl se recuperó y el incidente terminó limpiamente.

La práctica aburrida no solo arregló la máquina; preservó la narrativa. En entornos corporativos, la narrativa es la mitad de la recuperación: necesitas explicar qué pasó sin culpar a los rayos cósmicos.

Listas de verificación / plan paso a paso

Checklist A: Ves “Failed to get D-Bus connection” al ejecutar systemctl (ámbito sistema)

  1. Confirma que estás en el host y que PID 1 es systemd (Task 2).
  2. Comprueba el montaje y la capacidad de /run (Task 6).
  3. Verifica que /run/dbus/system_bus_socket exista (Task 3).
  4. Revisa systemctl status dbus dbus.socket (Task 4).
  5. Comprueba el socket privado de systemd /run/systemd/private (Task 18).
  6. Prueba la respuesta del bus con busctl --system list (Task 17).
  7. Extrae logs: journalctl -b y unidades relevantes (Task 12).
  8. Si debes reiniciar, hazlo deliberadamente: logind → dbus → daemon-reexec (Task 20).

Checklist B: Ves el error al ejecutar systemctl –user o herramientas de escritorio (ámbito sesión de usuario)

  1. Comprueba XDG_RUNTIME_DIR y DBUS_SESSION_BUS_ADDRESS (Task 7).
  2. Verifica que /run/user/UID y /run/user/UID/bus existan y sean propiedad del usuario (Task 8).
  3. Comprueba systemctl --user status (Task 9).
  4. Usa loginctl list-sessions y loginctl show-user (Task 10).
  5. Si esto ocurre por SSH/cron, decide: ¿necesitas una sesión real o un servicio del sistema?
  6. Si necesitas servicios de usuario en segundo plano, habilita lingering para ese usuario (Task 16), luego prueba de nuevo.
  7. Si el directorio runtime sigue desapareciendo, arregla el ciclo de vida de la sesión y PAM (Task 14/15).

Checklist C: Estás en automatización/CI y falla

  1. Confirma si estás en un contenedor y PID 1 no es systemd (Task 2).
  2. Deja de intentar usar systemctl en ese entorno. Ejecuta el servicio directamente o rediseña el job.
  3. Si realmente requieres systemd, ejecuta un entorno compatible con systemd de forma intencional, no accidental.

Broma #2: Reiniciar dbus sin comprobar sockets es como reiniciar una impresora porque te quedaste sin papel—cathártico, ineficaz y extrañamente popular.

Preguntas frecuentes

1) ¿Por qué usa systemctl D-Bus en absoluto?

systemctl es un cliente. Habla con las APIs del gestor systemd, comúnmente expuestas vía D-Bus y el socket privado de systemd. Sin bus, no hay conversación.

2) Veo dbus-daemon en ejecución. ¿Por qué sigo obteniendo el error?

Porque que el proceso exista no es lo mismo que el socket sea accesible en tu namespace/contexto. Comprueba las rutas de socket bajo /run y confirma que estés en el namespace de montaje del host (Task 3, 6, 19).

3) ¿Qué cambia entre “No such file or directory” y “Permission denied”?

No such file generalmente significa que la ruta del socket no existe en tu vista (montaje /run faltante, directorio runtime ausente, problema de namespace). Permission denied significa que el socket existe pero el control de acceso te bloquea (usuario incorrecto, política o confinamiento).

4) ¿Por qué solo falla por SSH?

O bien tu sesión SSH no está registrada con logind (misconfiguración de PAM), o estás ejecutando dentro de un wrapper/chroot restringido. Verifica pam_systemd.so y comprueba si se crea /run/user/UID para esa sesión (Task 10, 14).

5) ¿Es seguro habilitar lingering?

Es seguro cuando sabes por qué lo necesitas: ejecutar servicios de usuario sin inicios de sesión activos. Es inseguro como solución general porque mantendrás gestores de usuario vivos, lo que puede ocultar bugs de logout y desperdiciar recursos. Habilítalo por usuario, deliberadamente (Task 16).

6) ¿Puedo simplemente exportar DBUS_SESSION_BUS_ADDRESS y seguir?

Puedes, pero no deberías. Exportar direcciones obsoletas es como crear “funciona en mi shell” fantasmas que fallan después. Prefiere establecer una sesión real y dejar que logind/systemd ponga XDG_RUNTIME_DIR y la dirección del bus.

7) ¿Cuál es la forma más rápida de diferenciar bus del sistema vs bus de usuario?

Si usas systemctl sin --user, es ámbito del sistema. Si el socket relevante es /run/dbus/system_bus_socket, es el bus del sistema. Si es /run/user/UID/bus, es el bus de la sesión de usuario.

8) Estoy en una instalación de servidor mínima—¿necesito dbus-user-session?

Si ejecutas servicios en ámbito de usuario o esperas que las sesiones de usuario tengan un bus de sesión adecuado, sí, a menudo es necesario. Si solo gestionas servicios del sistema, a veces puedes evitarlo. Respuesta guiada por síntomas: si falta el bus de usuario, comprueba la presencia del paquete (Task 13).

9) ¿Por qué systemctl --user falla como root incluso cuando el usuario está conectado?

Porque el entorno de root no es el entorno del usuario, y root no está “adjunto” a ese bus de sesión de usuario. Ejecuta el comando como el usuario en la sesión, o usa herramientas adecuadas para apuntar a ese gestor de usuario.

10) ¿Cuándo reinicio en lugar de depurar?

Si PID 1 está poco saludable, /run está corrupto/solo lectura, o los sockets de systemd faltan y no puedes recuperarlos limpiamente, un reinicio controlado suele ser la solución más fiable. Captura logs primero.

Conclusión: próximos pasos que puedes desplegar hoy

“No se pudo obtener la conexión D-Bus” no es una invitación a reiniciar servicios al azar. Es una petición para verificar un contrato: /run está montado y escribible, el socket correcto existe, tu sesión es real y tu entorno apunta al bus correcto.

Haz esto a continuación:

  1. Ejecuta el guion rápido: sockets, directorios runtime, sesiones logind. No te saltes a los reinicios.
  2. Decide si tu flujo de trabajo depende del bus de usuario. Si es así, estandariza rutas de inicio (PAM + logind) y evita cron para trabajo de sesión.
  3. Si esto es un problema en la flota, añade una comprobación ligera: verificar que /run/dbus/system_bus_socket y /run/systemd/private existan, y alertar sobre directorios runtime faltantes para sesiones activas.
  4. Escribe la regla de contexto: chroots/containers pueden fallar en systemctl. Tus runbooks deberían decirlo claramente.

RAID no es respaldo: la frase que la gente aprende demasiado tarde

La llamada suele llegar cuando el panel de control está en verde y los datos han desaparecido. El arreglo está “saludable”. La base de datos está “en ejecución”.
Y, sin embargo, el director financiero mira un informe vacío, el equipo de producto mira un bucket vacío, y tú miras la frase que desearías haber tatuado en la orden de compra: RAID no es respaldo.

RAID es excelente en una cosa: mantener un sistema en línea ante ciertos tipos de fallo de disco. No está diseñado para protegerte contra
borrados, corrupción, ransomware, incendios, errores humanos, firmware roto, o la extraña y atemporal inclinación humana de ejecutar rm -rf
en la ventana equivocada.

Qué hace realmente RAID (y lo que nunca prometió)

RAID es un esquema de redundancia para la disponibilidad del almacenamiento. Eso es todo. Es una forma de seguir atendiendo lecturas y escrituras cuando un disco
(o a veces dos) deja de cooperar. RAID trata sobre continuidad del servicio, no continuidad de la verdad.

En términos de producción: RAID te compra tiempo. Reduce la probabilidad de que un fallo de disco único se convierta en una incidencia. Puede mejorar
el rendimiento según el nivel y la carga de trabajo. Puede simplificar la gestión de capacidad. Pero no crea una copia separada, independiente y versionada
de tus datos. Y la independencia es la palabra que mantiene tu trabajo.

Disponibilidad vs durabilidad vs recuperabilidad

La gente mezcla estas en un mismo cubo etiquetado “seguridad de datos”. No son lo mismo:

  • Disponibilidad: ¿puede el sistema seguir funcionando ahora mismo? RAID ayuda aquí.
  • Durabilidad: ¿permanecerán los bits correctos con el tiempo? RAID a veces ayuda, a veces engaña.
  • Recuperabilidad: ¿puedes restaurar un estado conocido y bueno después de un incidente? Eso es respaldo, snapshots, replicación y proceso.

RAID puede seguir sirviendo datos corrompidos. RAID puede reflejar fielmente tu borrado accidental. RAID puede replicar con entusiasmo extremo los bloques cifrados por ransomware.
RAID es un empleado leal. Leal no significa inteligente.

Qué significa “respaldo” en un sistema que puedes defender

Un respaldo es una copia separada de los datos que es:

  • Independiente del dominio de fallo primario (discos distintos, host distinto, idealmente cuenta/credenciales distintas).
  • Versionada para que puedas retroceder a antes de que ocurriera lo malo.
  • Restaurable dentro de un tiempo que puedas tolerar (RTO) y hasta un punto en el tiempo aceptable (RPO).
  • Probada, porque “tenemos backups” no es un hecho hasta que has restaurado desde ellos.

Snapshots y replicación son excelentes herramientas. No son automáticamente respaldos. Se convierten en respaldos cuando son independientes, están protegidos
contra los mismos errores administrativos y puedes restaurarlos bajo presión.

Broma #1: RAID es el cinturón de seguridad. El respaldo es la ambulancia. Si confías en el cinturón para realizar cirugía, vas a tener un día largo.

Por qué RAID falla como respaldo: los modos de fallo que importan

La razón por la que se repite “RAID no es respaldo” es que los modos de fallo no son intuitivos. El fallo de disco es solo un tipo de pérdida de datos.
Los sistemas modernos pierden datos por software, humanos y atacantes más a menudo que por un disco soltando su cereza SMART.

1) Borrado y sobrescritura son instantáneamente redundantes

Borra un directorio. RAID refleja el borrado. Sobrescribe una tabla. RAID distribuye esa nueva verdad por todo el conjunto. No hay “deshacer” porque el trabajo de RAID
es mantener las copias consistentes, no históricas.

2) Corrupción silenciosa, bit rot y la trampa de “todo parece bien”

Discos, controladoras, cables y firmware pueden devolver datos erróneos sin lanzar un error. Sistemas de archivos con checksums (como ZFS, btrfs) pueden
detectar corrupción y, con redundancia, a menudo autocurarse. RAID tradicional bajo un sistema de archivos que no suma checksums a nivel de bloque
puede devolver bloques corruptos y llamarlo éxito.

Incluso con checksums de extremo a extremo, aún puedes corromper datos a un nivel superior: escrituras de aplicación erróneas, compactación defectuosa, migraciones medio aplicadas.
RAID preservará la corrupción perfectamente.

3) Al ransomware no le importa tu paridad

El ransomware cifra lo que puede acceder. Si puede acceder a tu sistema de archivos montado, puede cifrar tus datos en RAID1, RAID10, RAID6,
espejos ZFS, o lo que sea. La redundancia no detiene el cifrado. Solo asegura que el cifrado esté altamente disponible.

4) Fallos de controladora y firmware se llevan el arreglo con ellos

El hardware RAID añade un dominio de fallo: la controladora, su módulo de caché, su firmware, su batería/supercap y su formato de metadatos.
Si la controladora muere, puede que necesites un modelo idéntico de controladora y el mismo nivel de firmware para ensamblar el arreglo limpiamente.

El RAID por software también tiene dominios de fallo (kernel, md metadata, herramientas en espacio de usuario), pero tienden a ser más transparentes y portables.
Transparente no significa seguro. Solo significa que puedes ver el cuchillo antes de pisarlo.

5) Las reconstrucciones son estresantes y empeoran a medida que los discos crecen

La reconstrucción es donde las matemáticas se encuentran con la física. Durante la reconstrucción, cada disco restante se lee intensamente, a menudo cerca del ancho de banda completo, durante horas o días.
Esa es una tormenta perfecta para sacar a la luz errores latentes en los discos restantes. Si pierdes otro disco en un RAID5 durante la reconstrucción, pierdes el arreglo.
RAID6 te da más margen, pero la reconstrucción sigue aumentando el riesgo y degradando el rendimiento.

6) Error humano: el modo de fallo más común y menos respetado

Un ingeniero cansado reemplaza el disco equivocado, saca la bandeja equivocada o ejecuta el comando correcto en el host equivocado. RAID no protege contra
humanos. Los amplifica. Un clic equivocado se replica a línea de velocidad.

7) Desastres en sitio y radio de impacto

RAID es local. El fuego también es local. También lo son el robo, los eventos de energía y “ups borramos toda la cuenta en la nube”. Una verdadera estrategia de backup asume
que perderás un dominio de fallo entero: un host, un rack, una región o una cuenta.

Datos interesantes y un poco de historia (la útil)

Algunos hechos concretos hacen que este tema cale porque muestran cómo RAID terminó siendo tratado como un hechizo mágico.
Aquí hay nueve, todas relevantes, ninguna romántica.

  1. RAID fue nombrado y popularizado en un artículo de UC Berkeley de 1987 que planteaba “redundant arrays of inexpensive disks” como alternativa a discos grandes y caros.
  2. El marketing temprano de RAID insistía en “tolerancia a fallos”, y mucha gente silenciosamente lo tradujo a “protección de datos,” que no es el mismo contrato.
  3. Los niveles de RAID nunca fueron un estándar único oficial. Los proveedores implementaron “RAID5” con comportamientos y políticas de caché diferentes, y luego discutían sobre semántica en tu ventana de incidencia.
  4. Las controladoras RAID de hardware históricamente usaban formatos de metadatos propietarios en disco, por eso el fallo de la controladora puede convertirse en arqueología.
  5. El auge de discos multi-terabyte hizo que las reconstrucciones de RAID5 fueran dramáticamente más arriesgadas porque el tiempo de reconstrucción creció y la probabilidad de encontrar un sector ilegible durante la reconstrucción aumentó.
  6. Las tasas URE (unrecoverable read error) se discutieron mucho en los 2000s como una razón práctica para preferir doble paridad en arreglos grandes, especialmente bajo carga de reconstrucción intensa.
  7. ZFS (lanzado por primera vez a mediados de los 2000s) introdujo checksums de extremo a extremo en operaciones mainstream y convirtió “bit rot” en una frase comprensible para la dirección porque finalmente podía detectarse.
  8. Las snapshots se volvieron comunes en el almacenamiento empresarial en los 1990s pero a menudo se almacenaban en el mismo arreglo—rollback rápido, no recuperación ante desastres.
  9. El ransomware cambió la conversación de “cinta vs disco” a “inmutabilidad vs credenciales”, porque los atacantes aprendieron a borrar backups primero.

Tres mini-historias corporativas desde las trincheras

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

Una compañía SaaS mediana ejecutaba su clúster PostgreSQL principal en un par de servidores de gama alta con hardware RAID10. El pitch del proveedor sonaba
reconfortante: discos redundantes, caché de escritura con batería, repuestos calientes. El equipo oyó “sin pérdida de datos” y mentalmente archivó los backups como “agradables de tener”.

Una tarde, un desarrollador ejecutó un script de limpieza contra producción. Se suponía que debía apuntar a un esquema de staging; apuntó al en vivo.
En segundos, se borraron millones de filas. La base de datos siguió atendiendo tráfico, y los gráficos de monitorización parecían bien—las consultas eran más rápidas, de hecho,
porque había menos datos.

Intentaron recuperar usando la “snapshot” de la controladora RAID, que no era una snapshot en el sentido del sistema de archivos. Era un perfil de configuración
para el comportamiento de caché. El proveedor de almacenamiento, para su crédito, no se rió. Simplemente hizo la pregunta que acaba carreras:
“¿Cuáles son sus últimos backups conocidos buenos?”

No había ninguno. Había un volcado lógico nocturno configurado meses atrás, pero escribía al mismo volumen RAID, y el script de limpieza borró también el directorio del volcado.
La compañía reconstruyó a partir de logs de aplicación y flujos de eventos de terceros. Recuperaron la mayoría, pero no todo, y pasaron semanas arreglando daños referenciales sutiles.

La suposición equivocada no fue “RAID es seguro.” Fue “la disponibilidad implica recuperabilidad.” Tenían alta disponibilidad y poca verdad.

Mini-historia 2: La optimización que se volvió en su contra

Una plataforma de medios estaba obsesionada con el rendimiento. Movieron los metadatos de su almacenamiento de objetos desde una configuración conservadora a un ancho RAID5
para exprimir más capacidad usable y mejor rendimiento de escritura en papel. También activaron un caching agresivo en la controladora para mejorar las tasas de ingestión.

En operación normal, se veía bien. Las profundidades de cola eran bajas. La latencia bajó. La dirección obtuvo su diapositiva de “eficiencia de almacenamiento” para el informe trimestral.
Todos durmieron mejor durante aproximadamente un mes.

Entonces un único disco empezó a lanzar errores de lectura intermitentes. El arreglo lo marcó como “falla predictiva” pero lo mantuvo en línea. Se inició una reconstrucción
a un repuesto caliente durante horas pico porque el sistema era “redundante”. Esa reconstrucción saturó los discos restantes. La latencia se disparó, los timeouts subieron,
y los reintentos de la aplicación crearon un bucle de realimentación.

A mitad de la reconstrucción, otro disco encontró un sector ilegible. RAID5 no puede manejar eso durante la reconstrucción. La controladora declaró que el disco virtual falló.
El resultado no fue solo tiempo de inactividad. Fue corrupción parcial de metadatos que hizo la recuperación más lenta y desagradable que un crash limpio.

La optimización no fue malvada; fue sin límites. Optimizaron por capacidad y rendimiento de benchmark, y pagaron por ello con riesgo de reconstrucción y un mayor radio de impacto.
Reemplazaron el diseño por doble paridad, movieron las ventanas de reconstrucción fuera de pico y—lo más importante—construyeron una canalización de backups fuera del arreglo
para que la próxima falla fuera aburrida.

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

Una firma de servicios financieros operaba un servicio de archivos usado por equipos internos. El almacenamiento era un conjunto espejo ZFS: simple, conservador, poco emocionante.
La parte emocionante fue su higiene de backups: snapshots nocturnas, replicación fuera de sitio a un dominio administrativo distinto y pruebas de restauración mensuales.
Todos se quejaban de las pruebas de restauración porque “eran pérdida de tiempo”. El gerente de SRE las hizo no opcionales de todos modos.

El portátil de un contratista fue comprometido. El atacante obtuvo acceso VPN y luego una credencial privilegiada que podía escribir en el recurso compartido de archivos.
Durante la noche, ransomware empezó a cifrar directorios de usuarios. Como el share estaba en línea y era escribible, el cifrado se propagó rápido.

ZFS hizo exactamente lo que se le pidió: almacenó los nuevos bloques cifrados con integridad. El espejo RAID aseguró que el cifrado fuera durable.
A la mañana siguiente, los usuarios encontraron sus archivos renombrados e ilegibles. El espejo estaba “saludable”. El negocio no lo estaba.

La firma desconectó el recurso compartido de red, rotó credenciales y verificó el objetivo de backup inmutable. Los backups estaban almacenados en un entorno separado
con permisos restringidos de eliminación y bloqueos de retención. El atacante no pudo tocarlos.

La restauración no fue mágica; fue practicada. Restauraron primero los directorios más críticos según una lista de prioridad preacordada, luego el resto
durante el día siguiente. El postmortem fue aburrido en el mejor sentido. La moraleja también fue aburrida: procesos rutinarios vencen a la redundancia sofisticada.

Guía de diagnóstico rápido: encuentra el cuello de botella y el radio de impacto

Cuando algo anda mal con el almacenamiento, los equipos pierden tiempo discutiendo si es “los discos” o “la red” o “la base de datos”.
El enfoque correcto es establecer: (1) qué cambió, (2) qué está lento, (3) qué es inseguro, y (4) en qué aún puedes confiar.

Primero: deja de empeorarlo

  • Si sospechas corrupción o ransomware, congela las escrituras donde puedas: remonta en solo lectura, detén servicios, revoca credenciales.
  • Si un arreglo está degradado y reconstruyendo, considera reducir la carga para evitar una segunda falla durante la reconstrucción.
  • Inicia un registro de incidente: comandos ejecutados, marcas temporales, cambios realizados. La memoria no es evidencia.

Segundo: identifica si esto es rendimiento, integridad o disponibilidad

  • Rendimiento: alta latencia, timeouts, profundidad de colas, iowait. Los datos pueden seguir siendo correctos.
  • Integridad: errores de checksum, corrupción a nivel de aplicación, cambios inesperados en archivos. El rendimiento puede verse bien.
  • Disponibilidad: dispositivos faltantes, arreglos degradados/fallidos, sistemas de archivos que no montan. El sistema está gritando.

Tercero: localiza el dominio de fallo rápidamente

  1. Host: logs del kernel, errores de disco, estado de la controladora.
  2. Pila de almacenamiento: RAID/mdadm/ZFS, salud del sistema de archivos, estado de scrub.
  3. Ruta IO: multipath, HBA, expander SAS, NICs, switches si es almacenamiento en red.
  4. Aplicación: planes de consulta, contención de locks, tormentas de reintentos.
  5. Postura de backup/recuperación: ¿tienes un punto de restauración limpio y alcanzable?

Cuarto: decide el objetivo

En una caída, debes elegir un objetivo para liderar:

  • Mantenerlo en ejecución (disponibilidad): estabilizar, aceptar modo degradado.
  • Proteger los datos (integridad): congelar escrituras, tomar copias forenses, restaurar desde un conocido bueno.
  • Recuperar el servicio (recuperabilidad): conmutar, reconstruir en otro lugar, restaurar backups.

Estos objetivos entran en conflicto. Fingir que no es cómo terminas con un sistema funcionando que sirve datos incorrectos.

Tareas prácticas con comandos: qué ejecutar, qué significa, qué decidir

A continuación hay tareas prácticas que puedes ejecutar en sistemas Linux para entender tu postura de redundancia y tu real recuperabilidad.
Cada tarea incluye: comando, salida de ejemplo, qué significa y la decisión que tomas a partir de ello.

Tarea 1: Comprueba los dispositivos de bloque actuales y membresía RAID

cr0x@server:~$ lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL,SERIAL
NAME    SIZE TYPE FSTYPE MOUNTPOINT MODEL            SERIAL
sda   3.6T disk       	        HGST_HUS726T4TAL  K8H1ABCD
├─sda1 512M part vfat   /boot/efi
└─sda2 3.6T part
sdb   3.6T disk       	        HGST_HUS726T4TAL  K8H1EFGH
└─sdb1 3.6T part
md0   3.6T raid1 ext4   /data

Qué significa: Tienes un dispositivo RAID1 de software md0 montado en /data, construido a partir de particiones.

Decisión: Si pensabas que tenías “backups”, no es así. Esto es solo redundancia. Confirma que la ubicación del backup está separada.

Tarea 2: Inspecciona salud de mdadm y estado de reconstrucción

cr0x@server:~$ cat /proc/mdstat
Personalities : [raid1]
md0 : active raid1 sdb1[1] sda2[0]
      3906886464 blocks super 1.2 [2/2] [UU]

unused devices: <none>

Qué significa: [UU] indica que ambos miembros están arriba. Durante la reconstrucción verías [U_] y una línea de progreso.

Decisión: Si está degradado, reduce la carga y planifica el reemplazo de disco. Además: toma un snapshot de backup ahora si no tienes uno fuera del host.

Tarea 3: Obtén información detallada de mdadm, incluyendo contadores de eventos

cr0x@server:~$ sudo mdadm --detail /dev/md0
/dev/md0:
           Version : 1.2
     Creation Time : Mon Oct  2 11:22:09 2023
        Raid Level : raid1
        Array Size : 3906886464 (3726.02 GiB 4000.79 GB)
     Used Dev Size : 3906886464 (3726.02 GiB 4000.79 GB)
      Raid Devices : 2
     Total Devices : 2
       State : clean
Active Devices : 2
Working Devices : 2
Failed Devices : 0
 Spare Devices : 0
           Name : server:0
           UUID : 1a2b3c4d:5e6f:7890:abcd:ef0123456789
         Events : 12891

    Number   Major   Minor   RaidDevice State
       0       8        2        0      active sync   /dev/sda2
       1       8       17        1      active sync   /dev/sdb1

Qué significa: “clean” es bueno, pero no dice nada sobre si los archivos son correctos, consistentes o restaurables.

Decisión: Usa esto para confirmar topología e identificar qué disco físico corresponde a qué miembro antes de tocar el hardware.

Tarea 4: Revisa logs del kernel para errores de IO y resets

cr0x@server:~$ sudo journalctl -k -S "1 hour ago" | egrep -i "ata|sas|scsi|i/o error|reset|timeout" | tail -n 8
Jan 22 10:11:41 server kernel: ata3.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x6 frozen
Jan 22 10:11:41 server kernel: ata3.00: failed command: READ DMA EXT
Jan 22 10:11:41 server kernel: blk_update_request: I/O error, dev sdb, sector 9175040 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
Jan 22 10:11:42 server kernel: ata3: hard resetting link
Jan 22 10:11:47 server kernel: ata3: link is slow to respond, please be patient
Jan 22 10:11:52 server kernel: ata3: SATA link up 6.0 Gbps (SStatus 133 SControl 300)

Qué significa: Resets de enlace y errores de IO son señales tempranas. Podría ser disco, cable, backplane o controladora.

Decisión: Trátalo como “integridad en riesgo”. Inicia un backup fresco si es posible; planifica mantenimiento y aislamiento de hardware.

Tarea 5: Consulta SMART y contadores clave

cr0x@server:~$ sudo smartctl -a /dev/sdb | egrep -i "SMART overall|Reallocated_Sector_Ct|Current_Pending_Sector|Offline_Uncorrectable|Power_On_Hours"
SMART overall-health self-assessment test result: PASSED
  5 Reallocated_Sector_Ct   0x0033   100   100   010    Pre-fail  Always       -       12
197 Current_Pending_Sector  0x0012   100   100   000    Old_age   Always       -       2
198 Offline_Uncorrectable   0x0010   100   100   000    Old_age   Offline      -       2
  9 Power_On_Hours          0x0032   089   089   000    Old_age   Always       -       41231

Qué significa: “PASSED” no es tranquilidad. Sectores pendientes/offline-uncorrectable importan más. Este disco está deteriorándose.

Decisión: Reemplaza proactivamente. Si estás en RAID5/6, el riesgo de reconstrucción aumenta; programa la reconstrucción con carga reducida y backups verificados.

Tarea 6: Para hardware RAID, revisa estado de controladora/disco virtual (ejemplo storcli)

cr0x@server:~$ sudo storcli /c0/vall show
Controller = 0
Status = Success
Description = Show Virtual Drives

DG/VD TYPE  State Access Consist Cache Cac sCC     Size Name
0/0   RAID5 dgrd  RW     No      RWBD  -   OFF  10.913 TB data_vd0

Qué significa: El disco virtual está dgrd (degradado). “Consist No” sugiere que se necesita una verificación de consistencia.

Decisión: Pausa escrituras no esenciales, identifica discos fallados/predictivos y asegúrate de tener un backup restaurable antes de reconstruir.

Tarea 7: Confirma la política de caché de escritura y el estado de batería/supercap

cr0x@server:~$ sudo storcli /c0 show battery
Controller = 0
Status = Success
Description = Battery Status

BatteryType = iBBU
Status = Failed
Replacement required = Yes

Qué significa: Si la protección de caché falla, las controladoras a menudo desactivan la caché write-back o arriesgan perder escrituras reconocidas si hay corte de energía.

Decisión: Espera cambios de rendimiento y posible riesgo de integridad si la política está mal configurada. Reemplaza la batería/supercap y revisa el modo de caché.

Tarea 8: Mide si estás limitado por CPU o por IO (iostat)

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

avg-cpu:  %user %nice %system %iowait  %steal   %idle
          12.34  0.00    5.12   31.45    0.00   51.09

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await aqu-sz  %util
md0              85.0   5420.0     0.0    0.0   18.20    63.76     40.0   3120.0   44.10   2.90   98.7

Qué significa: Alto %iowait y %util cercano a 100% indican cuello de botella en IO. La latencia de escritura es alta.

Decisión: Limita trabajos pesados, revisa si hay reconstrucción/scrub, y considera mover carga caliente fuera del arreglo mientras estabilizas.

Tarea 9: Encuentra qué procesos están golpeando el IO (iotop)

cr0x@server:~$ sudo iotop -oPa -n 5
Total DISK READ: 55.43 M/s | Total DISK WRITE: 12.10 M/s
  PID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN  IO>  COMMAND
18422 be/4   postgres  40.22 M/s   8.10 M/s  0.00 % 92.00 % postgres: checkpointer
27109 be/4   root      12.11 M/s   0.00 B/s  0.00 % 15.00 % rsync -aH --delete /data/ /mnt/backup/

Qué significa: Tu trabajo de backup y mantenimiento de base de datos están compitiendo. No es una fábula moral; es física.

Decisión: Reprograma backups/ventanas de mantenimiento o aplica limitación de velocidad para que los backups no causen incidentes (o al revés).

Tarea 10: Revisa errores del sistema de archivos rápidamente (ejemplo ext4)

cr0x@server:~$ sudo dmesg | egrep -i "EXT4-fs error|I/O error|Buffer I/O error" | tail -n 6
[915230.112233] EXT4-fs error (device md0): ext4_find_entry:1531: inode #524301: comm nginx: reading directory lblock 0
[915230.112240] Buffer I/O error on device md0, logical block 12345678

Qué significa: El sistema de archivos está viendo errores de lectura. RAID puede estar enmascarando algunos fallos, pero no todos.

Decisión: Detén servicios si es posible, captura logs, y planifica un fsck controlado (o una restauración) en lugar de dejar que la corrupción se propague.

Tarea 11: Verifica salud del pool ZFS y contadores de error

cr0x@server:~$ sudo zpool status -v
  pool: tank
 state: DEGRADED
status: One or more devices has experienced an unrecoverable error.
action: Replace the faulted device, or use 'zpool clear' to mark the device repaired.
  scan: scrub repaired 0B in 00:42:18 with 0 errors on Sun Jan 18 02:15:01 2026
config:

        NAME        STATE     READ WRITE CKSUM
        tank        DEGRADED     0     0     0
          mirror-0  DEGRADED     0     0     0
            sdc     FAULTED      0     0     8  too many errors
            sdd     ONLINE       0     0     0

errors: Permanent errors have been detected in the following files:

        tank/data/app.db

Qué significa: ZFS detectó errores de checksum y puede decirte qué archivo está afectado. Esta es la diferencia entre “creer” y “saber”.

Decisión: Trata los archivos nombrados como sospechosos. Restaura los datos afectados desde backup o replicación a nivel de aplicación; reemplaza el disco fallado.

Tarea 12: Comprueba snapshots ZFS y si los estás confundiendo con backups

cr0x@server:~$ sudo zfs list -t snapshot -o name,creation -s creation | tail -n 5
tank/data@hourly-2026-01-22-0600  Thu Jan 22 06:00 2026
tank/data@hourly-2026-01-22-0700  Thu Jan 22 07:00 2026
tank/data@hourly-2026-01-22-0800  Thu Jan 22 08:00 2026
tank/data@hourly-2026-01-22-0900  Thu Jan 22 09:00 2026
tank/data@hourly-2026-01-22-1000  Thu Jan 22 10:00 2026

Qué significa: Bien. Pero si estas snapshots viven en el mismo pool, no sobrevivirán a la pérdida del pool, compromiso de cuenta o fallo del sitio.

Decisión: Replica snapshots a un destino independiente con credenciales diferentes y protecciones de eliminación.

Tarea 13: Confirma que existen los backups y son recientes (ejemplo restic)

cr0x@server:~$ restic -r /mnt/backup/restic-repo snapshots --last
repository 9b2f1c12 opened (version 2, compression level auto)
ID        Time                 Host        Tags        Paths
a1b2c3d4  2026-01-22 09:00:14  server                  /data

Qué significa: Tienes una snapshot de backup de hoy. Eso es un comienzo.

Decisión: Valida la capacidad de restauración, no solo la existencia. Si las snapshots dejan de actualizarse, trátalo como incidente.

Tarea 14: Haz una restauración de prueba de un solo archivo para demostrar recuperabilidad

cr0x@server:~$ mkdir -p /tmp/restore-test && restic -r /mnt/backup/restic-repo restore latest --target /tmp/restore-test --include /data/important/report.csv
repository 9b2f1c12 opened (version 2, compression level auto)
restoring <Snapshot a1b2c3d4 of [/data] at 2026-01-22 09:00:14 by cr0x@server> to /tmp/restore-test
Summary: Restored 1 files/dirs (42.133 KiB) in 0:00

Qué significa: Este es el momento en que “tenemos backups” se convierte en un hecho. Restauraste exitosamente un archivo real.

Decisión: Haz de esto un ejercicio programado con selección aleatoria de archivos y resultados documentados.

Tarea 15: Comprueba si tu destino de “backup” es realmente independiente

cr0x@server:~$ mount | egrep "/data|/mnt/backup"
 /dev/md0 on /data type ext4 (rw,relatime)
 server:/export/backup on /mnt/backup type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2)

Qué significa: Los backups van a NFS. La independencia depende de dónde viva ese NFS y quién pueda eliminarlo.

Decisión: Si NFS está en el mismo servidor, mismo rack o con el mismo conjunto de credenciales administrativas, no es lo bastante independiente. Soluciona eso.

Tarea 16: Verifica retención e inmutabilidad en el nivel de sistema de archivos (chattr)

cr0x@server:~$ sudo lsattr -d /mnt/backup
-------------e---- /mnt/backup

Qué significa: No hay flags de inmutabilidad aquí. Puede estar bien, pero entonces la inmutabilidad debe venir del sistema de backup o del objetivo de almacenamiento.

Decisión: Si el ransomware está en tu modelo de amenaza (lo está), implementa bloqueos de retención/inmutabilidad fuera del alcance administrativo primario.

Tarea 17: Comprueba si estás a un typo de borrar los backups (permisos)

cr0x@server:~$ namei -l /mnt/backup/restic-repo | tail -n 4
drwxr-xr-x root root /mnt
drwxr-xr-x root root /mnt/backup
drwxrwxrwx root root /mnt/backup/restic-repo

Qué significa: Repositorio de backup escribible por todos. Eso no es un backup; es un proyecto de arte comunitario.

Decisión: Restringe permisos, separa credenciales de backup y considera objetivos solo-apendibles o inmutables.

Tarea 18: Detecta una reconstrucción o scrub que esté matando el rendimiento silenciosamente

cr0x@server:~$ sudo zpool iostat -v 1 3
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        2.10T  1.40T    820    210  92.1M  18.2M
  mirror-0                  2.10T  1.40T    820    210  92.1M  18.2M
    sdc                         -      -    420    105  46.0M   9.1M
    sdd                         -      -    400    105  46.1M   9.1M
--------------------------  -----  -----  -----  -----  -----  -----

Qué significa: Lecturas sostenidas altas pueden indicar scrub/resilver o un cambio de carga. Necesitas correlacionar con estado del pool y trabajos cron.

Decisión: Si esto coincide con dolor de usuarios, reprograma scrubs, ajusta prioridad de resilver o añade capacidad/margen de rendimiento.

Broma #2: Una reconstrucción RAID es el equivalente en almacenamiento de “solo un cambio rápido en producción.” Nunca es rápido, y definitivamente cambia las cosas.

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

Esta sección es intencionalmente específica. El consejo genérico no sobrevive a un incidente; solo termina citado en el postmortem.

1) “El arreglo está saludable, pero los archivos están corruptos”

  • Síntomas: Errores de aplicación al leer archivos específicos; desajustes de checksum a nivel de app; usuarios ven medios dañados; RAID muestra óptimo.
  • Causa raíz: Corrupción silenciosa en disco/controladora/cable, o la aplicación escribió malos datos. La paridad/espejado de RAID lo preservó.
  • Solución: Usa un sistema de archivos con checksums (ZFS) o checksums en la aplicación; ejecuta scrubs; restaura objetos corruptos desde backups independientes; reemplaza hardware defectuoso.

2) “No podemos reconstruir: segundo disco falló durante la reconstrucción”

  • Síntomas: Disco virtual RAID5 falla a mitad de reconstrucción; aparecen UREs; múltiples discos muestran errores de medios.
  • Causa raíz: Paridad simple más discos grandes más carga de lectura intensa durante reconstrucción; margen insuficiente para errores de sector latentes.
  • Solución: Prefiere RAID6/RAIDZ2 o espejos para arreglos grandes; mantén repuestos calientes; ejecuta lecturas de patrulla/scrubs; reemplaza discos proactivamente; asegúrate de tener backups restaurables antes de reconstruir.

3) “Los backups existen pero las restauraciones son demasiado lentas para cumplir el RTO”

  • Síntomas: El job de backup reporta éxito; la restauración tarda días; el negocio necesita horas.
  • Causa raíz: No se ingenió el RTO; ancho de banda al target de backup insuficiente; demasiados datos, poca priorización; no hay plan de restauración por niveles.
  • Solución: Define RTO/RPO por dataset; implementa recuperación local rápida (snapshots) más backups fuera de sitio; pre-posiciona datasets críticos; practica restauraciones parciales.

4) “Las snapshots nos salvaron… hasta que el pool murió”

  • Síntomas: Horario de snapshots confiable; luego pérdida catastrófica del pool; snapshots desaparecen con él.
  • Causa raíz: Snapshots almacenadas en el mismo dominio de fallo que los datos primarios.
  • Solución: Replica snapshots a un sistema/cuenta diferente; añade inmutabilidad; trata “mismo host” como “mismo radio de impacto.”

5) “Ransomware cifró producción y backups”

  • Síntomas: Repositorio de backup borrado/cifrado; retención purgada; credenciales usadas legítimamente.
  • Causa raíz: Sistema de backup escribible/borrable con las mismas credenciales comprometidas en producción; sin inmutabilidad/air gap.
  • Solución: Separa credenciales y activa MFA; roles de backup solo-escribir; bloqueo de objetos inmutables o destinos append-only; copia offline para el peor caso; monitoriza eventos de eliminación.

6) “El rendimiento colapsó después de reemplazar un disco”

  • Síntomas: Picos de latencia después del reemplazo; sistemas hacen timeouts; nada más cambió.
  • Causa raíz: Rebuild/resilver saturando IO; controladora limitando; modo degradado en arreglos de paridad.
  • Solución: Agenda ventanas de reconstrucción; limita la velocidad de rebuild; mueve cargas; añade discos/SSDs; mantén margen extra; no reconstruyas en pico a menos que disfrutes del caos.

7) “La controladora murió y no podemos importar el arreglo”

  • Síntomas: Los discos aparecen pero los metadatos del arreglo no se reconocen; la herramienta del proveedor no ve el disco virtual.
  • Causa raíz: Metadatos de hardware RAID ligados a familia/firmware de la controladora; fallo del módulo de caché; confusión por configuración extranjera.
  • Solución: Estandariza controladoras y mantén repuestos; exporta configuraciones de controladora; prefiere almacenamiento definido por software para portabilidad; y lo más importante, ten backups que no requieran que la controladora exista.

Listas de verificación / plan paso a paso: construye backups que sobrevivan a la realidad

Aquí está el plan que funciona cuando estás cansado, con poco personal y aún se espera que aciertes.
Es opinado porque la producción es opinada.

Paso 1: Clasifica los datos por consecuencia para el negocio

  • Tier 0: autenticación/identidad, facturación, datos de clientes, base de datos central.
  • Tier 1: herramientas internas, analítica, logs necesarios para seguridad/forense.
  • Tier 2: caches, artefactos de build, datasets reproducibles.

Si todo es “crítico”, nada lo es. Define RPO y RTO por tier. Escríbelo donde finanzas puedan verlo.

Paso 2: Elige la regla base y luego supérala

La regla clásica es 3-2-1: tres copias de los datos, en dos medios/tipos diferentes, con una copia fuera de sitio. Es un punto de partida, no una medalla.
Para ransomware, “fuera de sitio” también debe significar “no eliminable con las mismas credenciales”.

Paso 3: Separa los dominios de fallo a propósito

  • Hardware distinto: no “un directorio distinto”.
  • Límite administrativo distinto: cuentas/roles separadas; producción no debe tener permiso de borrar en backups.
  • Geografía distinta: al menos una copia fuera del sitio/rack/región que puedas perder.

Paso 4: Usa snapshots para velocidad, backups para supervivencia

Las snapshots locales son para recuperaciones rápidas de “ups”: borrados accidentales, despliegues malos, rollback rápido. Mantenlas frecuentes y con retención corta.
Los backups son para cuando la máquina, el arreglo o la cuenta han desaparecido.

Paso 5: Encripta y autentica la canalización de backup

  • Encripta en reposo y en tránsito (y maneja las claves como si importaran, porque importan).
  • Usa credenciales dedicadas de backup con permisos mínimos.
  • Prefiere rutas solo-escritura desde producción hacia backup cuando sea posible.

Paso 6: Haz de la retención una política, no una vibra

  • Corto: horario/diario para rollback rápido.
  • Medio: semanal/mensual para necesidades legales/empresariales.
  • Largo: trimestral/anual si es requerido, almacenado barato e inmutable.

Paso 7: Prueba restauraciones como si importara

El backup más caro es el que nunca restauras hasta el día que lo necesitas. Las pruebas de restauración deben programarse, registrarse y tener dueño.
Rota responsabilidades para que el conocimiento no viva en la cabeza de una sola persona.

Paso 8: Monitoriza las cosas correctas

  • Frescura del backup: hora de la última snapshot exitosa por dataset.
  • Integridad del backup: verificación periódica o restauración de prueba.
  • Eventos de eliminación: alertas por eliminaciones inusuales de backups.
  • Salud del almacenamiento: SMART, estado RAID, errores ZFS, resultados de scrub.

Paso 9: Ejecuta un tabletop para los escenarios feos

Practica:

  • Borrado accidental (restaurar un directorio).
  • Ransomware (asume que el atacante tiene admin de producción).
  • Fallo de controladora (asume que el arreglo primario es irrecuperable).
  • Pérdida del sitio (asume que todo el rack/región se perdió).

Paso 10: Decide para qué sirve el RAID (y deja de pedirle que sea un respaldo)

Usa RAID/espejos/codificación por borrado para cumplir objetivos de disponibilidad y rendimiento. Usa backups para cumplir objetivos de recuperabilidad.
Si la elección de RAID está impulsada por “no necesitamos backups,” estás haciendo arquitectura con pensamiento deseoso.

Una cita para tener sobre tu monitor

Parafraseando la idea: La esperanza no es una estrategia. — General Jim Mattis (citada a menudo en círculos de ingeniería y operaciones)

Si estás construyendo almacenamiento sobre esperanza, no estás construyendo almacenamiento. Estás construyendo un informe de incidente futuro con mucho tiempo de preparación.

FAQ

1) Si tengo RAID1, ¿todavía necesito backups?

Sí. RAID1 protege contra la falla de un disco. No protege contra borrado, corrupción, ransomware, bugs de controladora o pérdida del sitio.
RAID1 hace que el sistema siga en funcionamiento mientras ocurre lo equivocado.

2) ¿Las snapshots son un backup?

No automáticamente. Las snapshots son referencias en un punto en el tiempo, usualmente almacenadas en el mismo sistema. Se vuelven “parecidas a backups” solo cuando se replican
a un destino independiente con una retención que no puedas eliminar casualmente.

3) ¿Es RAID6 “suficientemente seguro” para saltarse los backups?

No. RAID6 reduce la posibilidad de pérdida del arreglo por fallos de discos durante la reconstrucción. No hace nada por fallos lógicos (borrar, sobrescribir),
malware o eventos catastróficos. Los backups existen porque el fallo de disco no es la única amenaza.

4) ¿Y el almacenamiento en la nube con redundancia: cuenta como respaldo?

La redundancia del proveedor cloud suele tratar la durabilidad de los objetos almacenados, no tu capacidad de recuperarte de tus propios errores.
Si borras o sobrescribes, la cloud lo hará de forma fiable. Aún necesitas versionado, bloqueos de retención y copias independientes.

5) ¿Cuál es el plan mínimo viable de backups para una empresa pequeña?

Empieza con: backups diarios a un destino independiente, al menos 30 días de retención y una copia fuera de sitio. Añade retención semanal/mensual según sea necesario.
Luego programa pruebas de restauración. Si solo haces una cosa “avanzada”, haz las pruebas de restauración.

6) ¿Con qué frecuencia deberíamos probar restauraciones?

Para sistemas críticos, mensualmente es una línea base razonable, con restauraciones parciales más frecuentes (semanal es excelente).
Después de cambios grandes—nuevo almacenamiento, nuevas claves de cifrado, nueva herramienta de backup—prueba inmediatamente.

7) ¿Cuál es la diferencia entre replicación y backup?

La replicación copia datos a otro lugar, a menudo en casi tiempo real. Eso es excelente para alta disponibilidad y bajo RPO, pero puede replicar cambios malos instantáneamente.
Los backups son versionados y retenidos para que puedas volver a antes del fallo. Muchos entornos usan ambos.

8) ¿Cómo protejo los backups del ransomware?

Separa credenciales y restringe la eliminación. Usa inmutabilidad/bloqueos de retención en el objetivo de backup. Mantén al menos una copia offline o en un dominio administrativo separado.
Monitoriza borrados sospechosos y deshabilita el acceso al repositorio de backup desde hosts de uso general.

9) ¿ZFS elimina la necesidad de backups?

ZFS mejora la integridad con checksums y autocuración (con redundancia), y las snapshots son excelentes para rollback rápido.
Pero ZFS no te impide borrar datos, cifrarlos o perder todo el pool. Aún necesitas backups independientes.

10) ¿Qué RPO/RTO deberíamos elegir?

Elige en función del dolor del negocio, no de lo que el equipo de almacenamiento desea que sea verdad. Para datos Tier 0, RPO de minutos/horas y RTO de horas podrían ser necesarios.
Para tiers inferiores, días pueden ser aceptables. La clave es que los números deben estar diseñados e probados, no declarados.

Próximos pasos que puedes hacer esta semana

RAID es una herramienta para mantenerse en línea ante ciertos fallos de hardware. No es una máquina del tiempo. No es un testigo de tribunal. No le importa
si los datos son correctos; le importa si los bits son consistentes entre discos.

Si gestionas sistemas de producción, haz estos pasos esta semana:

  1. Inventaría tu almacenamiento: nivel RAID, tipo de controladora, edades de discos y comportamiento de reconstrucción.
  2. Escribe RPO/RTO para tus tres datasets principales. Si no puedes, no tienes un plan de backups—tienes un plan de esperanza.
  3. Verifica independencia: confirma que los backups viven fuera del dominio de fallo primario y fuera de credenciales que se borran fácilmente.
  4. Ejecuta una prueba de restauración: un archivo, un directorio y (si te atreves) una restauración de base de datos a un entorno de prueba.
  5. Configura alertas para frescura de backups y anomalías de eliminación, no solo para salud de discos.

Luego, y solo entonces, disfruta tu RAID. Es útil cuando lo tratas con honestidad: como redundancia, no como salvación.

ZFS scrub lento: Cómo distinguir la lentitud normal de un problema real

Tu scrub ha estado “in progress” el tiempo suficiente como para que la gente pregunte si el almacenamiento está encantado. Las aplicaciones se sienten lentas, los paneles muestran una marea de I/O y la ETA o no existe o está mintiendo. Necesitas saber: ¿es un scrub normal y aburrido que está haciendo su trabajo, o es un síntoma de algo que te morderá después?

Esta es la forma orientada a producción de responder esa pregunta. Separaremos la lentitud esperada (la que programas y toleras) de la lentitud patológica (la que arreglas antes de que se convierta en un incendio de tickets de soporte).

Qué hace realmente un scrub (y por qué a veces “lento” es correcto)

Un ZFS scrub no es un benchmark ni una operación de copia. Es una patrulla de integridad de datos. ZFS recorre los bloques asignados en el pool, los lee, verifica checksums y—si la redundancia lo permite—repara corrupción silenciosa reescribiendo datos buenos sobre los malos. Es mantenimiento proactivo, del tipo “encuéntralo antes que el usuario”.

Eso implica dos cosas que sorprenden a la gente:

  • Los scrubs son fundamentalmente de lectura intensiva (con escrituras ocasionales cuando ocurren reparaciones). Tu pool puede estar “lento” porque las lecturas son lentas, porque hay contención con cargas reales, o porque ZFS intencionalmente está siendo educado.
  • Los scrubs operan a nivel de bloques, no a nivel de archivos. La fragmentación, las elecciones de recordsize y la sobrecarga de metadatos pueden importar más que el MB/s bruto del disco.

Los scrubs también se comportan diferente según la disposición de vdev. Los mirrors tienden a scrubear más rápido y de forma más predecible que RAIDZ, porque los mirrors pueden atender lecturas desde cualquiera de los lados y tienen una matemática de paridad más simple. Los RAIDZ scrubs están bien cuando están sanos, pero pueden convertirse en una caminata larga si tienes vdevs anchos, discos marginales o I/O aleatorio intenso de las aplicaciones.

Regla práctica que uso: el tiempo de scrub es una propiedad observable de tu sistema, no una falla moral. Pero la velocidad del scrub que colapsa, o una ETA que aumenta, es un olor a problema. No siempre un incendio, pero siempre digno de inspección.

Broma corta #1: Un scrub sin ETA es como una interrupción de almacenamiento sin postmortem—técnicamente posible, socialmente inaceptable.

Datos interesantes y un poco de historia

  • ZFS popularizó el checksumming end-to-end en el almacenamiento de servidor mainstream. Los checksums se almacenan separados de los datos, por eso ZFS puede detectar “discos mentirosos” que devuelven bloques corruptos sin errores I/O.
  • El scrub es la respuesta de ZFS a la “bit rot”—corrupción silenciosa e incremental que los RAID tradicionales a menudo no detectan a menos que ocurra una lectura y se dispare una reconstrucción por paridad.
  • El término “scrub” proviene de sistemas de almacenamiento más antiguos que escaneaban periódicamente el medio buscando errores. ZFS lo hizo rutinario y visible para el usuario.
  • RAIDZ fue diseñado para evitar el write hole visto en implementaciones clásicas de RAID5/6, manteniendo metadatos transaccionalmente consistentes y semántica copy-on-write.
  • ZFS nació en Sun Microsystems y luego se difundió ampliamente vía OpenZFS. El comportamiento moderno de ZFS depende de la versión de OpenZFS, no solo de “ZFS” como marca.
  • Los scrubs solían ser más dolorosos en sistemas sin buen scheduling de I/O o donde el throttling de scrub era primitivo. Las pilas modernas de Linux y FreeBSD te dan más palancas, pero también más formas de equivocarte.
  • Los metadatos importan. Pools con millones de archivos pequeños pueden scrubear más lento que un pool con menos archivos grandes, incluso si el “espacio usado” parece similar.
  • Los discos SMR hicieron los scrubs más impredecibles en el mundo real. Cuando el disco hace garbage collection shingled en segundo plano, las “lecturas” pueden convertirse en “lecturas más drama de reescritura interna”.
  • Las matrices empresariales han hecho lecturas de patrulla durante décadas, a menudo de forma invisible. ZFS solo te da la verdad en abierto—y resulta que la verdad puede ser lenta.

Lentitud normal del scrub vs problema real: el modelo mental

“Scrub lento” es ambiguo. Debes precisar qué tipo de lentitud estás viendo. Yo lo divido en cuatro cubetas:

1) Lentitud “gran pool, física normal”

Si tienes cientos de TB y discos giratorios, un scrub que toma días puede ser normal. Está limitado por el ancho de banda de lectura secuencial, la disposición de vdev y el hecho de que los scrubs no siempre obtienen patrones de acceso perfectamente secuenciales (los bloques asignados no son necesariamente contiguos).

Señales de que es normal:

  • La tasa de scrub es estable en el transcurso de horas.
  • La latencia de disco no se dispara.
  • El impacto en las aplicaciones es predecible y acotado.
  • No hay errores de checksum ni errores de lectura.

2) Lentitud “intencionalmente throttled”

ZFS a menudo se auto-limitirá en los scrubs para que las cargas de producción no se caigan. Eso significa que tu scrub puede verse decepcionantemente lento mientras el sistema sigue siendo usable. Esto es buen comportamiento de ingeniería. Puedes ajustarlo, pero hazlo deliberadamente.

Señales de que está limitando:

  • La CPU está mayormente bien.
  • IOPS no están saturados, pero el progreso del scrub avanza lentamente.
  • La latencia de la carga se mantiene dentro de los SLO.

3) Lentitud “por contención con la carga”

Si el pool atiende una base de datos ocupada, una granja de VM o una carga de objetos, las lecturas del scrub compiten con las lecturas/escrituras de las aplicaciones. Ahora la velocidad del scrub se convierte en una función de las horas de negocio. Eso no es una falla de ZFS; es un fallo de programación.

Señales de contención:

  • La velocidad del scrub varía con los patrones de tráfico.
  • Los picos de latencia se correlacionan con los picos de la aplicación.
  • Apagar el scrub hace felices a los usuarios otra vez.

4) Lentitud “algo está mal”

Esta es la categoría que realmente te preocupa. La lentitud del scrub se vuelve síntoma: un disco está reintentando lecturas, un controlador está registrando errores, un enlace negoció a 1.5Gbps, un miembro enfermo del vdev arrastra a todos, o hiciste un layout de pool que es bueno para capacidad pero malo para el comportamiento del scrub.

Señales de que probablemente hay un problema real:

  • Errores de lectura, errores de checksum o bytes “repaired” que aumentan entre scrubs.
  • Un disco muestra latencia mucho mayor o menor rendimiento que sus pares.
  • La tasa de scrub colapsa con el tiempo (empieza normal y luego se arrastra).
  • Los logs del kernel muestran resets, timeouts o problemas de enlace.
  • Los atributos SMART muestran sectores realocados/pending o errores UDMA CRC.

La clave: “lento” no es un diagnóstico. Estás cazando un cuello de botella y luego preguntando si ese cuello es esperado, configurado o está fallando.

Guion de diagnóstico rápido (primero/segundo/tercero)

Cuando estás on-call, no tienes tiempo para un largo seminario filosófico. Necesitas un embudo rápido que estreche el problema a uno de: esperado, contendido, throttled, o roto.

Primero: ¿El scrub está healthy?

  • Revisa el estado del pool en busca de errores y la tasa real del scrub.
  • Busca cualquier miembro de vdev que esté degraded, faulted o tenga “too many errors.”
  • Decisión: si hay errores, trata esto como un incidente de fiabilidad primero y una pregunta de rendimiento después.

Segundo: ¿Un dispositivo está arrastrando todo el vdev?

  • Revisa la latencia por disco y los tiempos de servicio de I/O mientras corre el scrub.
  • Revisa SMART rápidamente en busca de pending sectors, media errors y link CRC errors.
  • Decisión: si un disco está lento o reintentando, reemplázalo o al menos aísla; los scrubs son el canario.

Tercero: ¿Es contención o throttling?

  • Correlaciona la velocidad del scrub con métricas de la carga (IOPS, latencia, queue depth).
  • Chequea los tunables de ZFS y si el scrub está intencionalmente limitado.
  • Decisión: si estás throttled, ajusta con cuidado; si está contendido, reprograma o separa cargas.

Sólo después de esos tres llegas a “preguntas de arquitectura” como ancho de vdev, recordsize, vdevs especiales o añadir dispositivos cache. Si el scrub es lento porque un cable SATA es defectuoso, ningún “tuning” arregla la física.

Tareas prácticas: comandos, qué significa la salida y qué decisión tomar

Las siguientes tareas están diseñadas para ejecutarse mientras hay un scrub activo (o justo después). Cada una incluye un comando realista, salida de ejemplo, qué significa y la siguiente decisión. El prompt y las salidas son ilustrativos, pero los comandos son estándar en entornos reales.

Task 1: Confirmar estado del scrub, tasa y errores

cr0x@server:~$ zpool status -v tank
  pool: tank
 state: ONLINE
  scan: scrub in progress since Mon Dec 23 01:00:02 2025
        12.3T scanned at 612M/s, 8.1T issued at 403M/s, 43.2T total
        0B repaired, 18.75% done, 2 days 09:14:33 to go
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz2-0                  ONLINE       0     0     0
            sda                     ONLINE       0     0     0
            sdb                     ONLINE       0     0     0
            sdc                     ONLINE       0     0     0
            sdd                     ONLINE       0     0     0
            sde                     ONLINE       0     0     0
            sdf                     ONLINE       0     0     0

errors: No known data errors

Qué significa: ZFS muestra tanto “scanned” como “issued.” Issued está más cerca de la tasa real de finalización de I/O físico. Si issued es mucho menor que scanned, puedes estar viendo readahead, efectos de cache o espera por dispositivos lentos.

Decisión: Si los contadores READ/WRITE/CKSUM son distintos de cero, deja de tratar esto como “solo lento.” Investiga los dispositivos que fallan antes de tunear.

Task 2: Obtener progreso en una línea repetidamente (útil para canales de incidentes)

cr0x@server:~$ zpool status tank | sed -n '1,12p'
  pool: tank
 state: ONLINE
  scan: scrub in progress since Mon Dec 23 01:00:02 2025
        12.3T scanned at 612M/s, 8.1T issued at 403M/s, 43.2T total
        0B repaired, 18.75% done, 2 days 09:14:33 to go
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0

Qué significa: Este es el snippet mínimo viable de estado. Si la ETA sigue aumentando hora a hora, probablemente estés contendido o reintentando lecturas.

Decisión: Si la tasa issued es estable y la ETA disminuye constantemente, probablemente es normal o throttled. Si fluctúa salvajemente, pasa a chequear por-disco.

Task 3: Encontrar qué layout de vdev tienes

cr0x@server:~$ zpool status -P tank
  pool: tank
 state: ONLINE
config:

        NAME                                   STATE     READ WRITE CKSUM
        tank                                   ONLINE       0     0     0
          raidz2-0                             ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...A1   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...B2   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...C3   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...D4   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...E5   ONLINE       0     0     0
            /dev/disk/by-id/ata-ST12000...F6   ONLINE       0     0     0

Qué significa: RAIDZ2 en un vdev ancho. La velocidad del scrub estará limitada por el disco más lento y la sobrecarga de paridad. Un disco con mal comportamiento puede ralentizar todo el vdev.

Decisión: Si tienes un RAIDZ muy ancho y los scrubs son dolorosos, quizás necesites un cambio arquitectónico más adelante (más vdevs, anchura menor). No intentes “tunear” las leyes de la física.

Task 4: Chequear latencia y utilización por disco durante el scrub (Linux)

cr0x@server:~$ iostat -x 2 3
Linux 6.6.12 (server)     12/25/2025  _x86_64_    (32 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           4.21    0.00    2.73    8.14    0.00   84.92

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s  w_await aqu-sz  %util
sda             112.0   28800.0     0.0   0.00   18.40   257.1      2.0     512.0   4.30   2.10  98.5
sdb             118.0   30208.0     0.0   0.00   17.92   256.0      2.0     512.0   4.10   2.12  97.9
sdc             110.0   28160.0     0.0   0.00   19.30   256.0      2.0     512.0   4.20   2.05  98.2
sdd              15.0    3840.0     0.0   0.00  220.10   256.0      1.0     256.0  10.00   3.90  99.1
sde             115.0   29440.0     0.0   0.00   18.10   256.0      2.0     512.0   4.00   2.08  98.0
sdf             114.0   29184.0     0.0   0.00   18.70   256.0      2.0     512.0   4.20   2.11  98.4

Qué significa: sdd tiene r_await de ~220ms mientras los otros están ~18ms. Ese es tu ancla de scrub. El pool se moverá al ritmo del peor desempeño en un vdev RAIDZ.

Decisión: Inspecciona inmediatamente sdd en busca de errores/logs/SMART. Si es un problema de cable/controlador, arregla eso antes de reemplazar el disco.

Task 5: Revisar logs del kernel por resets/timeouts (Linux)

cr0x@server:~$ sudo dmesg -T | egrep -i 'ata|scsi|reset|timeout|error' | tail -n 12
[Wed Dec 24 13:18:44 2025] ata7.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x6 frozen
[Wed Dec 24 13:18:44 2025] ata7.00: failed command: READ FPDMA QUEUED
[Wed Dec 24 13:18:44 2025] ata7: hard resetting link
[Wed Dec 24 13:18:45 2025] ata7: SATA link up 1.5 Gbps (SStatus 113 SControl 300)
[Wed Dec 24 13:18:46 2025] sd 6:0:0:0: [sdd] tag#17 FAILED Result: hostbyte=DID_OK driverbyte=DRIVER_OK cmd_age=14s
[Wed Dec 24 13:18:46 2025] blk_update_request: I/O error, dev sdd, sector 123456789 op 0x0:(READ) flags 0x0 phys_seg 8 prio class 0

Qué significa: Reset de enlace más renegociación a 1.5Gbps es territorio clásico de “cable/backplane/puerto malo”. También puede ser un disco muriendo, pero los cables son más baratos y vergonzosamente comunes.

Decisión: Trata como fallo de hardware. Reasienta/reemplaza el cable o muévelo a otro puerto. Luego vuelve a revisar la latencia por disco. Si los errores persisten, reemplaza el disco.

Task 6: Chequeo rápido SMART para el dispositivo lento

cr0x@server:~$ sudo smartctl -a /dev/sdd | egrep -i 'Reallocated_Sector_Ct|Current_Pending_Sector|Offline_Uncorrectable|UDMA_CRC_Error_Count|SMART overall|Power_On_Hours'
SMART overall-health self-assessment test result: PASSED
  9 Power_On_Hours          0x0032   086   086   000    Old_age   Always       -       31245
  5 Reallocated_Sector_Ct   0x0033   100   100   010    Pre-fail  Always       -       0
197 Current_Pending_Sector  0x0012   100   100   000    Old_age   Always       -       12
198 Offline_Uncorrectable   0x0010   100   100   000    Old_age   Offline      -       3
199 UDMA_CRC_Error_Count    0x003e   200   199   000    Old_age   Always       -       27

Qué significa: Pending sectors y offline uncorrectables significan que el disco lucha por leer algunas áreas. Los UDMA CRC errors a menudo apuntan a problemas de cableado/backplane. “PASSED” no es absolución; es marketing.

Decisión: Si existen pending/offline uncorrectables, planifica el reemplazo. Si los CRC errors aumentan, arregla también la ruta (cable/backplane/HBA).

Task 7: Identificar si el pool está haciendo reparaciones (y cuánto)

cr0x@server:~$ zpool status -v tank | sed -n '1,25p'
  pool: tank
 state: ONLINE
  scan: scrub in progress since Mon Dec 23 01:00:02 2025
        14.8T scanned at 540M/s, 10.2T issued at 372M/s, 43.2T total
        256M repaired, 23.61% done, 2 days 05:01:12 to go
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz2-0  ONLINE       0     0     0

Qué significa: “repaired” distinto de cero durante el scrub significa que ZFS encontró mismatches de checksum y los corrigió. Ese es el scrub haciendo su trabajo, pero también es evidencia de corrupción en algún lugar (disco, cableado, controlador o memoria).

Decisión: Si las reparaciones son recurrentes entre scrubs, investiga la causa raíz. Una reparación única tras un evento conocido puede estar bien; reparaciones repetidas no lo están.

Task 8: Buscar indicadores de I/O y latencia a nivel ZFS (Linux)

cr0x@server:~$ sudo cat /proc/spl/kstat/zfs/arcstats | egrep '^(hits|misses|size|c_max|demand_data_misses|prefetch_data_misses) ' | head
hits                            2876543210
misses                          456789012
size                            17179869184
c_max                           34359738368
demand_data_misses              123456789
prefetch_data_misses            234567890

Qué significa: Las estadísticas ARC te dicen si las lecturas se sirven desde memoria o van al disco. Durante un scrub, el ARC puede ser menos útil porque las lecturas del scrub no siempre son cache-friendly, pero un ARC pequeño con cargas pesadas puede empeorar la contención.

Decisión: Si el ARC está reprimido y la caja está haciendo swap, arregla la presión de memoria primero. No culpes al scrub por un problema de RAM.

Task 9: Ver I/O del pool en tiempo real (Linux con zpool iostat)

cr0x@server:~$ zpool iostat -v tank 2 3
              capacity     operations     bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
tank        28.1T  15.1T  3.10K    220   380M  12.1M
  raidz2-0  28.1T  15.1T  3.10K    220   380M  12.1M
    sda          -      -    520     35  63.5M  2.1M
    sdb          -      -    525     36  64.1M  2.1M
    sdc          -      -    518     35  63.2M  2.0M
    sdd          -      -    110     34  12.9M  2.0M
    sde          -      -    523     35  64.0M  2.1M
    sdf          -      -    524     35  64.3M  2.1M
----------  -----  -----  -----  -----  -----  -----

Qué significa: De nuevo, sdd está rindiendo menos que los otros. En RAIDZ, eso puede reducir el throughput efectivo y aumentar el tiempo del scrub.

Decisión: Si un dispositivo muestra persistentemente menor ancho de banda/ops, céntrate allí. No ajustes tunables globales hasta que la paridad del dispositivo se restaure.

Task 10: Confirmar ashift y propiedades básicas del pool (baseline de rendimiento)

cr0x@server:~$ zdb -C tank | egrep 'ashift|vdev_tree' -n | head -n 8
64:        ashift: 12
120:    vdev_tree:
121:        type: 'root'
122:        id: 0

Qué significa: ashift: 12 implica sectores de 4K. Si ves ashift: 9 en discos modernos de 4K, puedes obtener amplificación de escritura y comportamiento extraño de rendimiento. No siempre se notará durante scrub (principalmente lecturas), pero puede empeorar el rendimiento general del pool y la sobrecarga de resilver/scrub.

Decisión: Si ashift está mal, la solución suele ser “reconstruir el pool correctamente”, no “tunar más”. Ponlo en la hoja de ruta.

Task 11: Verificar compresión y recordsize del dataset (interacción con la carga)

cr0x@server:~$ zfs get -o name,property,value -s local compression,recordsize tank/vmstore
NAME         PROPERTY     VALUE
tank/vmstore compression  lz4
tank/vmstore recordsize   128K

Qué significa: Para imágenes VM, recordsize suele establecerse más pequeño (como 16K) según patrones de I/O. Un recordsize grande no es “incorrecto”, pero si tu carga es random 4K, puedes terminar con más lecturas por byte útil durante el scrub y mayor sobrecarga operativa en general.

Decisión: No cambies recordsize a la ligera sobre datos existentes. Pero si el dolor del scrub se correlaciona con un dataset conocido por I/O aleatorio pequeño, revisa el diseño del dataset para la próxima iteración.

Task 12: Verificar vdevs especiales (metadatos) y su salud

cr0x@server:~$ zpool status tank | egrep -n 'special|log|cache|spares' -A3
15:    special
16:      nvme0n1p2             ONLINE       0     0     0

Qué significa: Si tienes un vdev special (a menudo NVMe) almacenando metadatos/bloques pequeños, su salud y latencia pueden dominar el comportamiento del scrub para pools con mucho metadata. Un vdev special moribundo puede hacer que todo el pool “se sienta” lento incluso si los HDD están bien.

Decisión: Si el scrub está lento en una carga con muchos metadatos, revisa el rendimiento y errores del vdev special temprano.

Task 13: Verificar la ruta real del dispositivo y la velocidad del enlace (falla oculta común)

cr0x@server:~$ sudo hdparm -I /dev/sdd | egrep -i 'Transport|speed|SATA Version' | head -n 5
Transport: Serial, ATA8-AST, SATA 3.1
SATA Version is:  SATA 3.1, 6.0 Gb/s (current: 1.5 Gb/s)

Qué significa: El disco soporta 6.0Gb/s pero actualmente está a 1.5Gb/s. Eso es un fuerte indicador de problemas de enlace, no de “ZFS lento”.

Decisión: Arregla la ruta física. Tras la reparación, confirma que negocia a 6.0Gb/s y vuelve a ejecutar iostat.

Task 14: Revisar parámetros relacionados con throttling del scrub (Linux OpenZFS)

cr0x@server:~$ sudo systool -m zfs -a 2>/dev/null | egrep 'zfs_scrub_delay|zfs_top_maxinflight|zfs_vdev_scrub_max_active' | head -n 20
  Parameters:
    zfs_scrub_delay        = "4"
    zfs_top_maxinflight    = "32"
    zfs_vdev_scrub_max_active = "2"

Qué significa: Estos valores influyen cuán agresivamente el scrub emite I/O. Más agresivo no siempre es mejor; puedes aumentar la profundidad de cola y la latencia para aplicaciones, y a veces ralentizar el scrub por thrashing.

Decisión: Si el scrub es lento pero healthy y tienes margen (baja latencia de impacto, baja util), puedes considerar tunear. Si el sistema ya está caliente, no “arregles” haciéndolo pelear más.

Task 15: Confirmar TRIM y comportamiento autotrim (pools SSD)

cr0x@server:~$ zpool get autotrim tank
NAME  PROPERTY  VALUE     SOURCE
tank  autotrim  off       default

Qué significa: En pools SSD, autotrim puede afectar el rendimiento a largo plazo. No afecta directamente la velocidad del scrub, pero cambia cómo se comporta el pool bajo lecturas/escrituras sostenidas y garbage collection, lo que puede hacer los scrubs “aleatoriamente horribles”.

Decisión: Si estás en SSDs y ves caídas periódicas de rendimiento, evalúa activar autotrim en una ventana de cambio controlada.

Task 16: Ver si accidentalmente estás scrubbeando con demasiada frecuencia

cr0x@server:~$ sudo grep -R "zpool scrub" -n /etc/cron* /var/spool/cron 2>/dev/null | head
/etc/cron.monthly/zfs-scrub:4: zpool scrub tank

Qué significa: Los scrubs mensuales son comunes. Los scrubs semanales pueden estar bien para pools pequeños, pero en pools grandes pueden significar que estás casi siempre en scrub, y los operadores empiezan a ignorar la señal.

Decisión: Ajusta la cadencia apropiada al medio y al riesgo. Si el scrub nunca termina antes del siguiente, has convertido las comprobaciones de integridad en ruido de fondo.

Tres micro-historias corporativas desde las trincheras del scrub

Micro-historia 1: El incidente causado por una suposición errónea

Una compañía mediana ejecutaba un clúster de virtualización respaldado por ZFS. Nada exótico: RAIDZ2, grandes discos SATA, un pool por nodo. Los scrubs se programaban mensualmente y siempre tomaban “un rato”. La gente lo aceptó como parte de la vida.

Entonces, un mes la ETA del scrub comenzó a aumentar. No fue dramático al principio—solo un día extra. El on-call asumió que era contención por la carga: trabajos batch de fin de trimestre. Lo dejaron pasar. “Los scrubs son lentos; terminará”.

Dos días después, la latencia de las VM de cara al usuario se disparó, luego se estabilizó, luego volvió a subir. zpool status aún mostraba ONLINE, sin errores obvios. La suposición continuó: “está ocupado”. Así que nadie miró las estadísticas a nivel de disco. Ese fue el error.

Cuando alguien finalmente ejecutó iostat -x, un disco tenía read await de 300–800ms, mientras los otros estaban en 15–25ms. SMART tenía pending sectors. El disco no fallaba rápido; fallaba educadamente, arrastrando al vdev entero mediante reintentos. Ese es el peor tipo porque parece “lentitud normal” hasta que deja de serlo.

Reemplazaron el disco. La tasa del scrub volvió a la normalidad de inmediato. La lección real no fue “reemplazar discos más rápido.” Fue: nunca asumas que la lentitud del scrub es la carga hasta que hayas probado que todos los dispositivos están sanos. El scrub es la única vez que algunos sectores malos se tocan. Es tu sistema de advertencia temprana. Úsalo.

Micro-historia 2: La optimización que salió mal

Otra organización tenía ventanas de mantenimiento estrictas. Querían que los scrubs terminaran durante un fin de semana, sin excepciones. Alguien encontró tunables de scrub y decidió “subirle.” Aumentaron la concurrencia del scrub y redujeron los delays. El scrub se volvió agresivo y el throughput se vio bien—por más o menos una hora.

Luego la latencia de las aplicaciones subió. Los hypervisors comenzaron a reportar stalls de almacenamiento. Los usuarios se quejaron el lunes por la mañana de “lentitud aleatoria.” El equipo culpó primero a la red (como hacen los equipos), luego a ZFS, luego al hypervisor. Triángulo clásico de la negación.

Lo que realmente pasó fue más aburrido: el patrón de I/O del scrub desplazó la cache de la carga y llenó las colas de disco. Los HDDs llegaron a casi 100% de util con altos tiempos de servicio. Algunas lecturas de la aplicación se convirtieron en monstruos de tail-latency. El scrub no terminó ni siquiera mucho más rápido en general—porque a medida que las colas crecían, el throughput efectivo cayó y aumentaron los reintentos.

Revirtieron el tuning y movieron los scrubs a periodos de menor tráfico. La “optimización” funcionó, pero el impacto a nivel sistema fue negativo. El mejor truco de rendimiento en almacenamiento sigue siendo la programación: no luches con tus usuarios.

Broma corta #2: Tunear almacenamiento es como política de oficina—si empujas demasiado, todos se ralentizan y de alguna forma sigue siendo tu culpa.

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

Un equipo fintech ejecutaba OpenZFS en Linux para una carga tipo ledger. Los scrubs se trataban como una actividad formal de mantenimiento: programados, monitorizados y comparados con históricos. Nada de heroísmos. Solo gráficos y disciplina.

Mantenían un runbook simple: después de cada scrub, registrar la duración, el ancho de banda emitido medio y cualquier byte reparado. Si “repaired” era distinto de cero, disparaba una comprobación más profunda: logs del kernel, SMART long test y revisión de cambios de hardware recientes.

Un mes, un scrub completó con una pequeña cantidad reparada—nada alarmante por sí solo. Pero fue el segundo mes consecutivo. Su lógica de baseline lo marcó. El on-call investigó y encontró CRC intermitentes en la ruta de un disco. No eran suficientes para que el disco fallara de inmediato, pero sí para voltear bits ocasionalmente bajo carga. Justo el tipo de defecto que arruina tu día seis meses después.

Cambiaron el cable del backplane y movieron ese disco a otro puerto HBA. Las reparaciones se detuvieron. Sin outage, sin pérdida de datos, sin un informe de incidente dramático. Este es el tipo de victoria que nunca se celebra porque nada explotó. Debería celebrarse de todos modos.

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

Esta sección es intencionalmente directa. Son patrones que aparecen en producción, repetidamente, porque los humanos son consistentes.

La ETA del scrub aumenta con el tiempo

  • Síntoma: ETA pasa de “12 horas” a “2 días” mientras corre el scrub.
  • Causa raíz: Un dispositivo reintenta lecturas (problemas de medio) o el enlace está flapping; alternativamente, la contención de la carga aumentó.
  • Solución: Ejecuta iostat -x y zpool iostat -v para identificar un disco lento; revisa dmesg y SMART. Si no hay un disco único lento, correlaciona con la carga y reprograma el scrub.

El scrub es “lento” solo durante horas laborales

  • Síntoma: El scrub se arrastra de 9–5 y acelera de noche.
  • Causa raíz: Contención con la carga de producción; ZFS y/o el scheduler del OS está priorizando I/O de primer plano.
  • Solución: Programa scrubs para ventanas de bajo tráfico; considera throttling en vez de agresividad. No subas la concurrencia del scrub esperando lo contrario.

Un disco muestra wait 10x mayor que los demás

  • Síntoma: En iostat -x, un disco tiene r_await alto o patrones de %util que no coinciden.
  • Causa raíz: Disco muriendo, comportamiento SMR bajo estrés, cable/backplane malo, puerto negociado a menor velocidad.
  • Solución: Revisa dmesg y SMART, confirma velocidad de enlace, intercambia cable/puerto, reemplaza disco si aparecen pending sectors o uncorrectables.

El scrub provoca timeouts en las aplicaciones

  • Síntoma: Picos de latencia, timeouts, crece la queue depth; el scrub parece “DoS” al sistema.
  • Causa raíz: I/O del scrub demasiado agresivo, mala aislamiento de cargas, pocos vdevs, pool HDD sirviendo I/O aleatorio sin suficientes spindles.
  • Solución: Reduce la agresividad del scrub; reprograma; añade vdevs o mueve carga a SSD/NVMe; considera vdevs special para casos con muchos metadatos. Deja de esperar que un RAIDZ ancho actúe como un array.

El scrub reporta bytes reparados repetidamente

  • Síntoma: Cada scrub repara algunos datos.
  • Causa raíz: Fuente crónica de corrupción: disco malo, cable malo, controlador inestable o memoria (sí, memoria).
  • Solución: Investiga el camino hardware end-to-end; ejecuta SMART long tests; revisa logs ECC si están disponibles; considera una ventana controlada para pruebas de memoria. Los datos reparados son un regalo—no los ignores.

El scrub es lento en un pool SSD “sin razón”

  • Síntoma: Pool NVMe/SSD scrubea más lento de lo esperado, a veces con caídas periódicas.
  • Causa raíz: Throttling térmico, garbage collection del SSD, mal comportamiento TRIM, problemas de enlace PCIe, o un vdev special estrangulado.
  • Solución: Revisa temperaturas y velocidad del enlace PCIe; evalúa autotrim; confirma firmware; asegura que el vdev special no esté saturado o con errores.

El scrub nunca termina antes del próximo scrub programado

  • Síntoma: Siempre scrubbeando; los operadores dejan de prestarle atención.
  • Causa raíz: Pool sobredimensionado para el medio dado, cadencia demasiado frecuente, o el scrub es reiniciado por automatización.
  • Solución: Reduce la cadencia; asegúrate de que los scrubs no se reinicien innecesariamente; considera cambios arquitectónicos (más vdevs, medios más rápidos) si las comprobaciones de integridad no completan en una ventana razonable.

La velocidad del scrub está muy por debajo de lo que la matemática de disco sugiere

  • Síntoma: “Tenemos N discos, cada uno puede hacer X MB/s, entonces ¿por qué no N×X?”
  • Causa raíz: El scrub lee bloques asignados, no necesariamente secuenciales; sobrecarga de metadatos; paridad RAIDZ; fragmentación; y el pool puede estar casi lleno, lo que empeora todo.
  • Solución: Compárate con tus propias líneas base históricas de scrub, no con hojas de datos del proveedor. Si estás casi lleno, libera espacio. Si la fragmentación es severa, considera un re-layout planificado mediante replicación a un pool nuevo.

Listas de verificación / planes paso a paso

Paso a paso: Decidir si un scrub lento es “normal”

  1. Captura el estado actual. Ejecuta zpool status -v. Guárdalo en tu ticket/chat.
  2. Mira si hay errores. Contadores READ/WRITE/CKSUM distintos de cero o cambios en “repaired” cambian la urgencia.
  3. Mide la tasa issued. Si issued es estable y dentro de tu rango histórico, probablemente es normal.
  4. Revisa la latencia por disco. Usa iostat -x (Linux) e identifica outliers.
  5. Revisa logs. Una línea en dmesg sobre resets puede explicar días de dolor en el scrub.
  6. Chequea SMART. Pending sectors, uncorrectables y CRC errors deciden si reemplazas hardware.
  7. Correlaciona con la carga. Si el scrub es lento solo bajo carga, arregla la programación y/o el throttling.
  8. Solo entonces tunear. Y haz un cambio a la vez con plan de rollback.

Paso a paso: Si encuentras un disco lento durante el scrub

  1. Confirma que está consistentemente lento: iostat -x 2 5 y zpool iostat -v 2 5.
  2. Revisa negociación de enlace abajo: hdparm -I en SATA, o logs del controlador para SAS.
  3. Revisa logs del kernel por resets/timeouts: dmesg -T filtrado.
  4. Revisa SMART: pending/offline uncorrectable sectors significan que vive en tiempo prestado.
  5. Intercambia lo barato primero (cable/puerto) si la evidencia apunta a problemas de enlace.
  6. Reemplaza el disco si hay problemas de medio o los errores persisten tras arreglos de la ruta.
  7. Tras reemplazar, ejecuta otro scrub o al menos un plan de verificación dirigido según tus estándares operativos.

Paso a paso: Si el scrub está healthy pero interrumpe rendimiento

  1. Confirma que ningún dispositivo está enfermo (latencia outlier, errores).
  2. Confirma si el scrub ya está throttled (revisa tunables y profundidad de I/O observada).
  3. Mueve la programación del scrub a periodos de baja carga; escalona entre pools/nodos.
  4. Si debes scrubear durante horas laborales, limita en vez de acelerar.
  5. Reevalúa el layout del pool si rutinariamente no puedes completar scrubs en la ventana de mantenimiento.

Preguntas frecuentes

1) ¿Cuál es una velocidad “normal” de scrub en ZFS?

Normal es lo que tu pool hace cuando está sano, con poca carga y sin errores. Usa tu propia duración histórica de scrub y el ancho de banda issued como baseline. Las especificaciones secuenciales del proveedor no son una promesa de scrub.

2) ¿Por qué difiere scanned de issued en zpool status?

“Scanned” refleja el progreso lógico a través de bloques; “issued” refleja el I/O real enviado/completado a los vdevs. Grandes brechas pueden ocurrir por caching, readahead o espera por dispositivos lentos. Si issued es bajo y la latencia es alta, busca un disco ancla.

3) ¿Un scrub lee el espacio libre?

Generalmente, el scrub verifica bloques asignados (lo que realmente está en uso). No es un escaneo total de superficie de cada sector. Por eso un disco puede aún tener sectores malos latentes que solo aparecen al escribir o leer más tarde.

4) ¿Debo parar un scrub si está lento?

Si el scrub está healthy pero impacta los SLO de producción, pausar/parar puede ser razonable—y luego reprogramarlo. Si ves errores o reparaciones, detenerlo solo retrasa información que probablemente necesitas. Atiende la causa hardware subyacente en su lugar.

5) ¿Con qué frecuencia debo hacer scrub?

La cadencia común es mensual para pools HDD grandes, a veces semanal para entornos más pequeños o de mayor riesgo. La respuesta depende del medio, la redundancia y cuán rápido quieres descubrir errores latentes. Si tu cadence supera tu capacidad para terminar scrubs, ajusta—no normalices “siempre en scrub”.

6) El scrub encontró y reparó datos. ¿Estoy a salvo ahora?

Estás más seguro que antes, pero no “terminaste”. Las reparaciones significan que algo se corrompió bajo ZFS. Si las reparaciones se repiten, necesitas un RCA de discos, cables, controladores y potencialmente memoria.

7) ¿RAIDZ es inherentemente lento en scrubs comparado con mirrors?

Los mirrors suelen ser más rápidos y predecibles en lecturas porque pueden balancear carga y no hacen reconstrucción por paridad en lecturas. RAIDZ puede estar bien cuando está sano, pero los vdevs RAIDZ anchos son más sensibles a un disco lento y a patrones I/O aleatorios.

8) ¿Puede el tuning hacer los scrubs dramáticamente más rápidos?

A veces modestamente, si tienes margen y defaults conservadores. Pero el tuning no sustituye a más spindles, mejor medio o arreglar una ruta defectuosa. Además: el tuning puede salir mal y aumentar latencia y reducir throughput efectivo.

9) ¿Por qué el scrub es lento en un pool mayormente vacío?

Porque “vacío” no significa “simple”. Un pool con millones de archivos pequeños, mucho metadata, snapshots o fragmentación puede scrubear lento aun si el espacio usado es bajo. El scrub toca bloques asignados; las asignaciones con mucho metadata no son secuenciales.

10) ¿Cuál es la diferencia entre scrub y resilver, y por qué importa para la lentitud?

El scrub verifica datos existentes y repara corrupción; la resilver reconstruye datos a un dispositivo reemplazado/retornado. La resilver suele tener prioridad y patrones distintos, y puede ser más escritora. Si confundes ambos, malinterpretarás expectativas de rendimiento y urgencia.

Conclusión: pasos prácticos siguientes

Los scrubs lentos no son inherentemente alarmantes. De hecho, un scrub lento en un pool grande y ocupado a menudo indica que ZFS se comporta de forma responsable. Lo que da miedo es la lentitud sin explicación, especialmente cuando viene con outliers por disco, resets del kernel o reparaciones recurrentes.

Usa esta secuencia como tu predeterminada:

  1. Ejecuta zpool status -v y decide si esto es un evento de fiabilidad (errores/reparaciones) o un problema de programación/rendimiento.
  2. Ejecuta iostat -x y zpool iostat -v para encontrar el dispositivo lento o confirmar contención.
  3. Revisa dmesg y SMART por fallos obvios en la ruta hardware.
  4. Solo entonces considera ajustes de tunning y cambios de programación, y mide el impacto contra tu baseline histórico.

Una idea parafraseada de W. Edwards Deming encaja en el trabajo operativo: “Without data, you’re just someone with an opinion.” La lentitud del scrub es tu oportunidad para recopilar datos antes de recopilar outages.

Debian 13: Servicio no arranca tras cambiar la configuración — arréglalo leyendo las líneas de registro correctas (caso n.º 1)

Cambiaste una configuración. Hiciste lo responsable. Incluso dejaste un comentario como “temporal” que sin duda seguirá ahí en 2027. Ahora el servicio no arranca, tu monitorización está avisando y systemctl status se muestra esquivo.

La buena noticia: Debian 13 junto con systemd te da todo lo necesario para resolver esto rápidamente—si dejas de leer las líneas de registro equivocadas. La mala noticia: la mayoría de la gente hace exactamente eso, se queda mirando las últimas tres líneas de salida y luego empieza sacrificios rituales a “la caché”. No lo hagas. Lee las líneas correctas, en el orden correcto, y lo solucionarás en minutos.

Caso n.º 1: cambio de configuración → el servicio no arranca (qué ocurrió realmente)

Este es el patrón más habitual que veo en sistemas Debian: un servicio está sano, alguien edita un archivo de configuración y luego reinicia el servicio. El reinicio falla. El responsable abre systemctl status, ve “failed with result ‘exit-code’” y empieza a adivinar.

La solución casi siempre está dentro de los registros, pero no en la parte que la gente lee primero. La línea útil suele ser:

  • Anterior a la línea “Main process exited…”
  • Proveniente de un proceso auxiliar (como ExecStartPre) que probó la configuración y salió
  • O del propio demonio, emitida una vez y luego enterrada bajo la verborrea de systemd

Para el caso n.º 1, imagina un servicio típico con un paso de prueba de configuración:

  • ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on;
  • ExecStart=/usr/sbin/nginx -g daemon on; master_process on;

El reinicio falla no porque systemd sea misterioso, sino porque la prueba previa detectó un error de sintaxis, una ruta de include inválida o un problema de permisos en un archivo referenciado. Las “líneas de registro correctas” son las que describen esa falla previa. Tu trabajo es extraerlas limpiamente, sin ahogarte en ruido no relacionado.

Broma #1 (corta y relevante): Un reinicio de servicio es como un paracaídas: si te saltas la inspección, igual descubrirás si funciona.

Algunos hechos e historia que explican por qué los registros se ven así

Entender por qué Debian 13 se comporta de esta manera te hace más rápido bajo presión. Aquí hay hechos concretos que importan cuando un servicio se niega a arrancar tras un cambio de configuración:

  1. systemd se convirtió en el sistema init por defecto de Debian en Debian 8 (Jessie). Esa decisión estandarizó la gestión de servicios y las expectativas de registro, pero también cambió dónde buscan errores las personas.
  2. journald no es un archivo de texto. Los registros se almacenan en un journal binario y se consultan con journalctl. Aún puedes reenviarlos a syslog, pero la fuente canónica es el journal.
  3. systemctl status es un resumen, no una investigación. Muestra una porción recortada de registros y el estado de la unidad a alto nivel. Sirve para apuntarte a consultas más profundas, no para sustituirlas.
  4. Las unidades systemd pueden tener múltiples procesos antes de que arranque el “daemon” real. ExecStartPre, generators, scripts wrapper y archivos de entorno pueden fallar antes de que exista siquiera el PID del servicio.
  5. Los códigos de salida están estandarizados, pero a menudo son engañosos sin contexto. Un “exit status 1” puede significar “error de sintaxis”, “permiso denegado” o “puerto ya en uso”. Necesitas el mensaje que lo acompaña.
  6. Muchos demonios están diseñados para fallar rápido con configuración inválida. Nginx, Postfix, HAProxy y otros se niegan intencionadamente a arrancar si las pruebas de configuración fallan—porque ejecutar con configuración parcial/incorrecta es peor.
  7. El empaquetado de Debian tiende a añadir comprobaciones de seguridad. Los mantenedores frecuentemente incluyen validaciones previas en las unidades o scripts wrapper. Es buena ingeniería, pero significa que los errores pueden venir de scripts que no sabías que estaban en la ruta.
  8. El orden de los registros puede ser engañoso. journald usa marcas temporales, pero el arranque paralelo de unidades y múltiples procesos puede entrelazar entradas. La “última línea” no es siempre “la causa”.
  9. El limitador de tasa es real. journald puede limitar por tasa servicios que spamean; el primer error puede registrarse y los siguientes 500 resumirse. Si solo ves el resumen, te pierdes la primera pista.

Una idea para tener en mente, parafraseada correctamente y atribuida: Gene Kim (idea parafraseada): la fiabilidad mejora cuando construyes bucles de retroalimentación rápidos y acortas la distancia entre el cambio y el diagnóstico.

Guía rápida de diagnóstico (primeras/segundas/terceras comprobaciones)

Este es el orden que gana en producción. Está orientado a obtener la causa raíz en menos de cinco minutos, no a dar la sensación de estar ocupado.

Primero: confirma qué cree systemd que falló (visión a nivel de unidad)

  • Obtén el estado de la unidad, el código de salida y en qué fase falló (pre-start vs inicio principal).
  • Extrae la línea de comando exacta que systemd ejecutó (incluyendo ExecStartPre).

Segundo: extrae el fragmento correcto del journal (por tiempo y por unidad)

  • Consulta los registros de esa unidad, para el último arranque, con el menor ruido posible.
  • Luego amplía el rango temporal si es necesario; no amplíes el ámbito primero.
  • Busca la primera línea de error significativa, no la última línea “exited”.

Tercero: ejecuta la propia validación de configuración del demonio manualmente

  • La mayoría de servicios tiene un modo “probar configuración y salir”.
  • Ejecuta exactamente como lo haría systemd (mismo usuario, mismo entorno, misma ruta de configuración).
  • Si la validación pasa manualmente pero falla bajo systemd, sospecha de permisos, archivos de entorno, AppArmor o diferencias en el directorio de trabajo.

Cuarto: decide entre arreglar, revertir o aplicar un bypass temporal

  • Si es un error de sintaxis claro: arréglalo ahora y reinicia.
  • Si es incierto y la producción está ardiendo: revierte a la última configuración conocida buena y reinicia.
  • Evita bypass temporales como comentar pasos de validación a menos que entiendas el radio de impacto.

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

Estas tareas están escritas como trabaja realmente un SRE: ejecuta un comando, lee la salida, toma una decisión. Sin discursos motivacionales. Cada tarea incluye qué significa la salida y qué haces después.

Tarea 1: Comprueba el estado de la unidad (pero léelo correctamente)

cr0x@server:~$ systemctl status nginx.service --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2025-12-30 10:14:03 UTC; 42s ago
   Duration: 2.103s
    Process: 21984 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE)
        CPU: 29ms

Dec 30 10:14:03 server nginx[21984]: nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57
Dec 30 10:14:03 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 10:14:03 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 10:14:03 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Qué significa: La falla ocurrió en ExecStartPre, antes del arranque del daemon nginx. Eso es una falla de prueba de configuración, no un crash en tiempo de ejecución.

Decisión: No persigas puertos, archivos PID o límites del kernel. Corrige la línea de configuración referenciada (app.conf:57) y vuelve a ejecutar la prueba de configuración.

Tarea 2: Muestra solo el journal para esta unidad (el último intento, limpio)

cr0x@server:~$ journalctl -u nginx.service -b --no-pager -n 60
Dec 30 10:14:03 server systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...
Dec 30 10:14:03 server nginx[21984]: nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57
Dec 30 10:14:03 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 10:14:03 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 10:14:03 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Qué significa: El journal confirma el error de parseo exacto. No hace falta inferir.

Decisión: Abre el archivo, corrige la sintaxis y vuelve a probar la configuración antes de reiniciar.

Tarea 3: Extrae registros “desde el reinicio” cuando el arranque está ruidoso

cr0x@server:~$ systemctl show -p ActiveEnterTimestampMonotonic nginx.service
ActiveEnterTimestampMonotonic=81234567890
cr0x@server:~$ journalctl -u nginx.service -b --no-pager --since "2 min ago"
Dec 30 10:14:03 server systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...
Dec 30 10:14:03 server nginx[21984]: nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57
Dec 30 10:14:03 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 10:14:03 server systemd[1]: nginx.service: Failed with result 'exit-code'.

Qué significa: Estás acotando los registros por tiempo en lugar de vadear por todo el arranque.

Decisión: Si el error no está en esa ventana, amplía a 10 minutos; no quites todavía el filtro por unidad.

Tarea 4: Inspecciona la unidad en busca de comprobaciones previas e archivos de entorno

cr0x@server:~$ systemctl cat nginx.service
# /lib/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server
After=network-online.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on;
ExecStart=/usr/sbin/nginx -g daemon on; master_process on;
ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target

Qué significa: La falla no está dentro del daemon de larga ejecución; está en el paso de validación. Además, no hay EnvironmentFile= aquí, por lo que hay menos variables ocultas.

Decisión: Ejecuta el comando exacto de pre-start manualmente para reproducirlo; si falla, arregla la configuración. Si tiene éxito, la falla es ambiental (permisos, AppArmor, rutas de include).

Tarea 5: Ejecuta la prueba de configuración del daemon manualmente (mismo comando)

cr0x@server:~$ sudo /usr/sbin/nginx -t -q -g "daemon on; master_process on;"
nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57

Qué significa: Este es un error determinista de parseo de configuración. No hay rareza de systemd.

Decisión: Corrige el archivo y la línea referenciada. No reinicies repetidamente esperando que “se arregle”. No lo hará.

Tarea 6: Localiza la línea ofensiva y valida la estructura de includes

cr0x@server:~$ nl -ba /etc/nginx/sites-enabled/app.conf | sed -n '45,70p'
    45  server {
    46      listen 443 ssl;
    47      server_name app.example.internal;
    48      include /etc/nginx/snippets/tls.conf;
    49
    50      location / {
    51          proxy_pass http://127.0.0.1:8080;
    52          proxy_set_header Host $host;
    53      }
    54
    55  }   # end server
    56
    57  }

Qué significa: Hay una llave de cierre extra en la línea 57.

Decisión: Elíminala, guarda, vuelve a ejecutar la prueba de configuración. Si ves desajustes de llaves a menudo, adopta una regla de estilo: un bloque por archivo, indentación consistente y un linter de configuración en CI.

Tarea 7: Valida de nuevo, luego reinicia (no omitas la validación)

cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Qué significa: Ahora es seguro reiniciar.

Decisión: Reinicia una vez. Si el reinicio falla ahora, es un problema distinto—no asumas que sigue siendo la sintaxis.

cr0x@server:~$ sudo systemctl restart nginx.service
cr0x@server:~$ systemctl is-active nginx.service
active

Qué significa: El servicio está en ejecución.

Decisión: Confirma que sirve tráfico (comprobación de salud local) y cierra el incidente correctamente.

Tarea 8: Cuando status no ayuda, muestra registros completos filtrando por prioridad

cr0x@server:~$ journalctl -u nginx.service -b -p warning --no-pager
Dec 30 10:14:03 server nginx[21984]: nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/app.conf:57

Qué significa: Filtraste a warnings y peores, así que no estás leyendo el relleno de “Started…”.

Decisión: Usa esto cuando una unidad es muy habladora. Si no aparece nada en warning/error, o registras en otro lugar o tienes una falla silenciosa antes de que el registro se inicialice.

Tarea 9: Confirma qué archivos de configuración cambiaron recientemente (caza al culpable real)

cr0x@server:~$ sudo find /etc/nginx -type f -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort | tail -n 8
2025-12-30 10:12 /etc/nginx/sites-enabled/app.conf
2025-12-29 18:41 /etc/nginx/nginx.conf
2025-12-10 09:03 /etc/nginx/snippets/tls.conf
2025-11-21 15:22 /etc/nginx/mime.types

Qué significa: Puedes correlacionar la falla de arranque con la edición más reciente.

Decisión: Si el error referencia un archivo incluido, revisa también su mtime. “Solo cambié una línea” rara vez es toda la historia.

Tarea 10: Si no es sintaxis, comprueba permiso denegado (clásico tras “hardening”)

cr0x@server:~$ journalctl -u nginx.service -b --no-pager -n 30
Dec 30 10:20:11 server nginx[22310]: nginx: [emerg] open() "/etc/nginx/snippets/tls.conf" failed (13: Permission denied)
Dec 30 10:20:11 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
cr0x@server:~$ namei -l /etc/nginx/snippets/tls.conf
f: /etc/nginx/snippets/tls.conf
drwxr-xr-x root root /
drwxr-xr-x root root etc
drwxr-xr-x root root nginx
drwx------ root root snippets
-rw------- root root tls.conf

Qué significa: Los permisos del directorio impiden que nginx (que corre como www-data después del arranque) o su comprobación previa lean los includes.

Decisión: Corrige permisos al mínimo requerido. Normalmente: bit de ejecución en directorios para recorrido y lectura de archivo para el usuario o grupo del servicio.

Tarea 11: Valida el usuario de runtime y el sandboxing del servicio

cr0x@server:~$ systemctl show nginx.service -p User -p Group -p DynamicUser -p ProtectSystem -p ReadWritePaths
User=
Group=
DynamicUser=no
ProtectSystem=no
ReadWritePaths=

Qué significa: Esta unidad en particular no usa directivas de sandboxing de systemd. Si ves ProtectSystem=strict o ReadWritePaths restrictivos, las lecturas/escrituras de configuración pueden estar bloqueadas.

Decisión: Si el sandboxing está habilitado, alinéalo con las necesidades del daemon en lugar de desactivarlo a ciegas. Añade ReadOnlyPaths/ReadWritePaths explícitas en un override.

Tarea 12: Interpreta las razones de fallo desde la perspectiva de systemd (códigos y señales)

cr0x@server:~$ systemctl show nginx.service -p ExecMainStatus -p ExecMainCode -p Result
ExecMainStatus=1
ExecMainCode=exited
Result=exit-code

Qué significa: El proceso terminó normalmente con estado 1. No fue SIGKILL, ni OOM, ni timeout.

Decisión: Concéntrate en configuración, parámetros y permisos. Si ves ExecMainCode=killed o Result=timeout, esa es otra rama completamente distinta.

Tarea 13: Si el servicio está flapeando, detén el bucle de reinicios mientras lees los registros

cr0x@server:~$ sudo systemctl reset-failed nginx.service
cr0x@server:~$ sudo systemctl stop nginx.service
cr0x@server:~$ systemctl status nginx.service --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: inactive (dead)

Qué significa: Evitas que systemd inunde con reinicios mientras depuras. Esto también hace que el journal sea más fácil de leer.

Decisión: Haz esto cuando Restart=always esté generando ruido y carga. Luego reinicia deliberadamente cuando tengas un arreglo.

Tarea 14: Compara cambios de configuración de forma segura con metadata de dpkg (revisión de empaquetado)

cr0x@server:~$ dpkg -S /etc/nginx/nginx.conf
nginx-common: /etc/nginx/nginx.conf
cr0x@server:~$ sudo ls -l /etc/nginx/nginx.conf*
-rw-r--r-- 1 root root 1492 Dec 29 18:41 /etc/nginx/nginx.conf
-rw-r--r-- 1 root root 1479 Nov 21 15:22 /etc/nginx/nginx.conf.dpkg-dist

Qué significa: Puede que tengas un archivo nuevo por defecto del distro o una fusión pendiente. Eso puede interactuar con tu cambio.

Decisión: Si el servicio empezó a fallar tras una actualización más un cambio de configuración, examina .dpkg-dist/.dpkg-old y reconcilia de forma intencional.

Tarea 15: Cuando faltan registros, confirma persistencia y limitador de journald

cr0x@server:~$ sudo grep -E '^(Storage|SystemMaxUse|RateLimitIntervalSec|RateLimitBurst)=' /etc/systemd/journald.conf | sed '/^#/d;/^$/d'
Storage=auto
RateLimitIntervalSec=30s
RateLimitBurst=1000
cr0x@server:~$ journalctl --disk-usage
Archived and active journals take up 384.0M in the file system.

Qué significa: Si Storage=volatile, pierdes registros al reiniciar. Si el limitador de tasa es bajo, podrías perder errores repetidos.

Decisión: En producción, persiste los logs en disco y asigna un tamaño apropiado. Para depuración, aumenta temporalmente los límites de tasa si un servicio spamea, pero arregla el spam después.

Broma #2 (corta y relevante): “Funcionó ayer” no es evidencia; es solo el testimonio de un testigo con mala memoria.

Tres micro-historias corporativas (y lo que enseñan)

Micro-historia 1: El incidente causado por una suposición errónea

El equipo tenía una flota Debian con una mezcla de servicios web y API. Una tarde, se desplegó un cambio rutinario de configuración: actualizar los cifrados TLS y estandarizar entre entornos. Alguien reinició nginx en un canario. Falló. Ejecutaron nginx -t manualmente; pasó. La suposición se formó al instante: “systemd está roto en este host”.

Se buceó en versiones de paquetes, parámetros del kernel e incluso en SELinux (que ni siquiera estaba activado). Mientras tanto, el tráfico se drenó del nodo y el autoscaler se puso nervioso. Siguieron reintentando reinicios “solo para ver”, que es una excelente manera de sobreescribir la única buena línea de error con una pila de reinicios.

La solución fue vergonzosamente simple: la unidad systemd usaba una ruta de configuración diferente a través de un archivo de entorno. No era malintencionado—solo histórico. El nginx -t manual probó /etc/nginx/nginx.conf; systemd probó /etc/nginx/nginx-canary.conf. El archivo canario incluyó un snippet que no existía en ese host.

La lección no es “no uses archivos de entorno”. Es: nunca asumas que tu reproducción manual coincide con el gestor de servicios. Extrae el ExecStartPre/ExecStart exacto de systemctl cat y ejecuta eso. Si existe un archivo de entorno, imprímelo y deja de adivinar.

Micro-historia 2: La optimización que salió mal

Un grupo de plataforma decidió “acelerar despliegues” cambiando de restart a reload cuando fuera posible. Reload es más barato: menos churn de conexiones, menos errores transitorios. Buena intención. Luego lo generalizaron a múltiples servicios con un script “one-size-fits-most”.

Un servicio, un broker de mensajes, aceptaba señales de reload pero solo recargaba parcialmente la configuración. Para algunas opciones requería un reinicio completo, pero el comando reload devolvía éxito de todas maneras. Con el tiempo, se acumuló drift de configuración: la configuración en memoria no coincidía con la del disco, y la gente dejó de confiar en ambas.

Finalmente, un cambio introdujo un parámetro que habría fallado la validación de un arranque limpio. El reload no hizo nada útil, dijo “OK” y el sistema siguió con la configuración antigua. Días después, un reinicio rutinario del host ocurrió. Ahora el servicio tenía que iniciar en frío, leyó la mala configuración y se negó a arrancar. Esta falla ocurrió durante una ventana de mantenimiento, que es donde vas a encontrarte con tus errores futuros.

La lección: reload no es gratis. Si eliges reload como optimización, también debes imponer la validación de configuración como parte del proceso de cambio y realizar reinicios controlados periódicamente para probar que la configuración realmente es arrancable.

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

Un servicio interno del área financiera corría en Debian, respaldado por una base de datos y un frontend web. Tenían una política de cambios poco glamurosa: cada edición de configuración debía ser commiteada en un repo, y las herramientas de despliegue siempre ejecutaban la prueba de configuración del servicio antes de tocar systemd. Si la prueba fallaba, el cambio simplemente no se desplegaba.

La gente se quejaba. “Nos ralentiza”. “Puedo probarlo en mi cabeza”. Lo usual. Pero llegó un día en que un ingeniero senior editó la configuración en vivo durante un incidente—porque el servicio se comportaba mal y necesitaban una mitigación rápida. La edición tenía un error sutil de comillas. El siguiente reinicio habría matado el servicio por completo.

La herramienta de despliegue se negó a aplicar el cambio sin pasar la prueba de configuración. Ese era el punto: guardarraíles cuando el estrés hace a todos descuidados. Arreglaron la comilla, volvieron a probar y luego reiniciaron con seguridad. Nadie recibió otra página.

La lección: la práctica aburrida no es el repo. Es la puerta de validación automática más una ruta de rollback predecible. Esas dos cosas evitan que pequeños errores se conviertan en incidentes.

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

Aquí están los reincidentes. Si tu servicio no arranca tras un cambio de configuración, probablemente caerás en uno de estos grupos.

1) Síntoma: systemctl status muestra “failed (Result: exit-code)” sin error útil

Causa raíz: Solo estás viendo el resumen. La línea significativa está antes o está truncada.

Solución: Consulta el journal directamente y amplía el fragmento.

cr0x@server:~$ journalctl -u myservice.service -b --no-pager -n 200
...look for the first real error line...

2) Síntoma: el servicio falla instantáneamente tras reiniciar; los logs mencionan ExecStartPre

Causa raíz: La validación previa falló (sintaxis, include faltante, directiva inválida).

Solución: Ejecuta la misma validación manualmente y corrige la configuración antes de reiniciar.

cr0x@server:~$ systemctl cat myservice.service | sed -n '/ExecStartPre/p'
ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on;

3) Síntoma: la prueba de configuración pasa manualmente, falla bajo systemd

Causa raíz: Ruta de configuración diferente, usuario distinto, entorno distinto o restricciones del sandbox.

Solución: Extrae el comando y entorno exactos de la unidad; ejecútalo como el usuario del servicio.

cr0x@server:~$ systemctl show myservice.service -p Environment -p EnvironmentFiles
Environment=
EnvironmentFiles=/etc/default/myservice (ignore_errors=no)

4) Síntoma: “Permission denied” en includes, certificados, sockets, archivos PID

Causa raíz: Cambio de hardening (chmod/chown), nueva ruta con permisos restrictivos o desajuste del usuario del servicio.

Solución: Rastrea permisos con namei -l; corrige bits de ejecución de directorio y la legibilidad de archivos.

5) Síntoma: “Address already in use” tras cambiar la configuración

Causa raíz: Cambiaste bind/listen; otro servicio ya lo ocupa; o la instancia antigua no se detuvo limpiamente.

Solución: Identifica quién tiene el puerto; decide si revertir el puerto, detener el servicio en conflicto o arreglar la activación por socket.

cr0x@server:~$ sudo ss -ltnp | grep ':443 '
LISTEN 0      511          0.0.0.0:443        0.0.0.0:*    users:(("haproxy",pid=1203,fd=7))

6) Síntoma: la unidad muestra Result=timeout

Causa raíz: El daemon se queda colgado durante el arranque (esperando DNS, almacenamiento, migraciones) o el timeout de systemd es demasiado agresivo para un arranque en frío.

Solución: Lee los registros alrededor del bloqueo y ajusta TimeoutStartSec solo si el trabajo de inicio es legítimo y acotado.

7) Síntoma: tras un cambio de configuración, el servicio “arranca” pero no funciona

Causa raíz: Usaste reload y asumiste que aplicó todo; o la configuración fue aceptada pero es semánticamente incorrecta.

Solución: Ejecuta un chequeo de salud a nivel de aplicación y confirma la configuración activa con introspección del servicio si está disponible. Reinicia si es necesario.

8) Síntoma: el journal no tiene entradas para la unidad

Causa raíz: El servicio registra en archivo (o stdout está redirigido), journald es volátil, o la unidad nunca se ejecutó por una falla de dependencia.

Solución: Revisa systemctl list-dependencies y la configuración de journald; inspecciona archivos de log tradicionales si el servicio los usa.

Listas de verificación / plan paso a paso (correcciones seguras y rollback)

Paso a paso: diagnostica y arregla sin sobrecargar el sistema

  1. Detén el bucle de reinicios si está presente. Si la unidad está flapeando, pausa para poder leer registros estables.
  2. Lee el resumen de la unidad. Identifica si ExecStartPre falló o el proceso principal murió.
  3. Consulta journald por unidad y por arranque. No comiences con registros globales.
  4. Extrae los comandos de arranque exactos. Lee systemctl cat y busca drop-ins.
  5. Ejecuta la prueba de configuración del servicio manualmente. Mismos args, misma ruta de configuración.
  6. Corrige lo más pequeño que haga que arranque. Evita refactors durante la respuesta a un incidente.
  7. Reinicia una vez y verifica a nivel de aplicación. “active (running)” no es lo mismo que “sirviendo”.
  8. Escribe la línea de causa raíz. Pega la cadena de error exacta en la nota del incidente. El tú del futuro agradecerá al tú del presente.

Plan de rollback: cuando no estás seguro de que tu arreglo sea correcto

Si no puedes probar el arreglo rápidamente, haz rollback. No “iteres en producción” mientras suena el pager.

  1. Guarda la configuración rota. Cópiala con timestamp para análisis posterior.
  2. Restaura la última conocida buena desde el repo de configuración o backup.
  3. Valida la configuración. Siempre ejecuta el modo de prueba del daemon.
  4. Reinicia el servicio y verifica.
  5. Solo tras la recuperación: depura el cambio roto en un entorno controlado.
cr0x@server:~$ sudo cp -a /etc/nginx/sites-enabled/app.conf /root/app.conf.broken.$(date +%F-%H%M%S)
cr0x@server:~$ sudo cp -a /root/rollback/app.conf /etc/nginx/sites-enabled/app.conf
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl restart nginx.service
cr0x@server:~$ systemctl is-active nginx.service
active

Cuando debes mantener parcialmente el servicio en marcha (control de daños)

A veces no puedes arreglarlo completamente de inmediato, pero puedes reducir el impacto:

  • Restaura una configuración mínima que sirva una página de mantenimiento.
  • Deshabilita el virtual host roto mientras los demás siguen funcionando.
  • Redirige tráfico fuera del nodo temporalmente, arregla en aislamiento y luego reintroduce.

No deshabilites pasos de validación para “hacer que arranque” a menos que estés seguro de que el daemon no arrancará en un estado corrupto. Ese camino conduce a pérdida de datos y no disfrutarás el postmortem.

Preguntas frecuentes

1) ¿Por qué no basta con systemctl status?

Porque es intencionalmente compacto. Muestra una pequeña cola de registros y un resumen del estado de la unidad. Úsalo para encontrar el nombre de la unidad, la fase de fallo y luego pivota a journalctl -u para el diagnóstico real.

2) ¿Cuál es el comando journalctl más útil para esta situación?

Normalmente:

cr0x@server:~$ journalctl -u myservice.service -b --no-pager -n 200

Si eso es ruidoso, añade -p warning o restringe por tiempo con --since.

3) ¿Cómo sé si la falla es de configuración o de runtime?

Busca ExecStartPre fallando (config/validación) frente al proceso principal que arranca y luego muere (runtime). systemctl status suele decirte qué proceso falló.

4) ¿Por qué a veces la prueba manual de configuración tiene éxito cuando systemd falla?

Entorno distinto. systemd puede usar un archivo de entorno, un directorio de trabajo distinto, sandboxing o un contexto de usuario diferente. Reproduce siempre usando la línea de comando exacta de la unidad.

5) ¿Cómo veo los drop-in overrides que podrían cambiar el comportamiento?

cr0x@server:~$ systemctl status myservice.service --no-pager
...look for "Drop-In:" lines...
cr0x@server:~$ systemctl cat myservice.service
...includes /etc/systemd/system/myservice.service.d/*.conf if present...

6) ¿Cuándo debo usar reload en lugar de restart?

Sólo cuando el servicio documente que reload aplica los cambios que hiciste y tengas un paso de validación. Si no estás seguro, reinicia en una ventana segura o tras drenar tráfico.

7) ¿Y si no hay registros en absoluto para la unidad?

Entonces o la unidad no se ejecutó, journald no retiene registros o los logs van a otro lugar (como /var/log/*). Revisa dependencias y ajustes de journald, e inspecciona logs basados en archivos si el servicio los usa.

8) ¿Cómo detecto rápidamente si es un problema de permisos?

Busca “Permission denied” en el journal y luego traza la ruta con namei -l. Los problemas de permisos suelen venir de bits de ejecución en directorios o cambios de hardening que olvidaron al usuario del servicio.

9) ¿Cuál es la forma más segura de prevenir esta clase de incidentes?

Automatiza la validación de configuración (modo test del daemon) antes de restart/reload, guarda las configuraciones en control de versiones y facilita el rollback. El objetivo es atrapar la línea rota antes de que llegue a systemd.

Conclusión: siguientes pasos para evitar incidentes recurrentes

Un servicio en Debian 13 que falla tras un cambio de configuración rara vez es un misterio. Suele ser una línea de error precisa que no extrajiste limpiamente. Lee la unidad para saber qué se ejecutó. Lee el journal acotado a la unidad para saber qué falló. Luego valida la configuración manualmente usando el comando exacto que systemd usa.

Pasos prácticos siguientes:

  • Añade un paso de prueba de configuración previo al despliegue para cada servicio que lo soporte.
  • Entrena a tu equipo para tratar systemctl status como un puntero, no como un diagnóstico.
  • Haz del rollback una operación de primera clase (copiar, restaurar, validar, reiniciar).
  • Estandariza una guía de “diagnóstico rápido” y tenla cerca de la rotación del pager.

Haz eso, y la próxima vez que un servicio se niegue a arrancar, pasarás el tiempo arreglando el problema real—no discutiendo con una pantalla de resumen.