Todo va bien hasta que deja de ir. Tu host Debian 13 funciona sin problemas durante horas, luego aparece un trabajo por lotes, la memoria se llena de caché sucio y la máquina pasa de “responsiva” a “¿por qué el tecleo de SSH va en cámara lenta?”. El iowait se dispara, los hilos kworker se encienden y tu base de datos comienza a expirar como si estuviera en un drama.
Esto es la tormenta de writeback: un vaciado sincronizado de demasiadas páginas sucias, que ocurre demasiado tarde y en el peor momento. La solución no suele ser “comprar discos más rápidos”. Es hacer que Linux comience el writeback antes, en porciones más pequeñas, y verificar que no has cambiado rendimiento por riesgo de datos.
Cómo se ve una tormenta de writeback (y por qué Debian 13 la hace evidente)
Una tormenta de writeback no es “el disco es lento”. Es un problema de sincronización: el kernel permite que una gran cantidad de memoria acumule páginas sucias (datos de archivos modificados en caché que aún no están en disco), y luego tiene que vaciarlas. Si se vacía demasiado de golpe, se produce contención por todas partes:
- Los escritores en primer plano se ven repentinamente muy limitados, a menudo en ráfagas.
kswapdo la presión de reclaim fuerza writeback al mismo tiempo.jbd2(journal de ext4) o los commits del registro del sistema de archivos se acumulan.- Los hilos sensibles a la latencia (bases de datos, workers de API) se bloquean por IO o por bloqueos del sistema de archivos.
En Debian 13, probablemente estés ejecutando un kernel moderno con mejor visibilidad y, a veces, comportamiento de reclaim/writeback más agresivo que flotas antiguas de larga vida. Eso es bueno: ves el problema antes. También es malo: las proporciones predeterminadas que “funcionaban” en discos giratorios de la era Debian 10 pueden convertirse en acantilados de latencia con NVMe porque el sistema escribe muy rápido… hasta que deja de hacerlo.
Una verdad operativa: las tormentas de writeback rara vez son aleatorias. Se repiten cuando el patrón de trabajo es repetible (trabajos ETL, backups, compactaciones, ráfagas de logs, descargas de imágenes de contenedor, artefactos de CI). Eso es una ventaja. Úsala.
Broma #1: El kernel es optimista: asume que tu disco podrá manejar todas esas escrituras más tarde. “Más tarde” es cuando estás de guardia.
Conceptos básicos sobre páginas sucias: qué hace realmente el kernel
Linux utiliza la memoria libre de forma agresiva como caché de páginas. Cuando las aplicaciones escriben en archivos (sin usar IO directo), a menudo escriben primero en memoria. Esas páginas se vuelven “sucias”. Más tarde, el writeback en segundo plano las vacía al disco. Esto da gran rendimiento y desacopla la velocidad de la aplicación de la velocidad del disco—hasta que alcanzas límites.
Términos clave que importan en producción
- Páginas sucias: datos de archivos en caché modificados en RAM pero aún no persistidos al almacenamiento.
- Writeback: el proceso del kernel que vacía páginas sucias al disco, en segundo plano o bajo presión.
- Umbral de writeback en segundo plano: cuando los hilos en segundo plano comienzan a escribir datos sucios de forma proactiva.
- Límite dirty: cuando los procesos que generan datos sucios son limitados (o forzados a escribir).
- Tiempo de expiración: cuánto tiempo se permite que los datos sucios permanezcan antes de que el writeback intente empujarlos.
Los controles están en /proc/sys/vm/, especialmente:
vm.dirty_background_ratioyvm.dirty_ratiovm.dirty_background_bytesyvm.dirty_bytesvm.dirty_expire_centisecsyvm.dirty_writeback_centisecs
Ratios frente a bytes (elige un estilo, no mezcles a la ligera)
Los ratios son porcentajes de “memoria disponible” (no estrictamente de la RAM total; el kernel usa contabilidad interna que cambia con la presión de memoria). Los bytes son umbrales absolutos. En producción, los bytes son más previsibles entre máquinas y en escenarios con presión de memoria, especialmente en hosts con muchos contenedores donde la “memoria libre” es un objetivo móvil.
Si fijas dirty_bytes, los knobs de ratio se ignoran efectivamente (lo mismo para background bytes). Normalmente eso es lo que quieres: menos sorpresas.
Qué significa realmente “riesgo de datos” aquí
Ajustar vm.dirty* no cambia la corrección del sistema de archivos ni las garantías de journaling. Cambia cuánto dato se permite tener sucio en RAM y cuánto tiempo puede permanecer allí. Los riesgos son operativos:
- Límites dirty más grandes pueden mejorar el rendimiento pero aumentan la cantidad de datos “en riesgo” durante una pérdida de alimentación (datos no aún volcados), y pueden producir tormentas de writeback más grandes y dolorosas.
- Límites dirty más pequeños reducen el tamaño de la tormenta y la exposición de datos sucios, pero pueden limitar a los escritores antes y reducir el rendimiento máximo.
La mayoría de equipos no necesitan rendimiento heroico. Necesitan latencia predecible. Ajusta en consecuencia.
Una cita relacionada con fiabilidad, porque corresponde aquí. Charity Majors dijo: “No puedes mejorar lo que no mides.” Ese es todo el juego con las tormentas de writeback: medir primero, ajustar después.
Hechos e historia: cómo llegamos aquí
- Hecho 1: Los kernels Linux tempranos usaban un comportamiento de caché de buffer mucho más simple; el writeback moderno evolucionó mucho alrededor de la era 2.6 con mecanismos por bdi (dispositivo de respaldo) para gestionar mejor la presión de IO.
- Hecho 2: Los ratios dirty predeterminados históricamente asumían “un disco razonable” y memoria en escala humana. Esos valores envejecieron mal cuando hosts con 128–512 GB de RAM se hicieron normales.
- Hecho 3: El throttling de dirty es una herramienta de latencia que viste un disfraz de rendimiento: existe para evitar que el sistema ensucie memoria “infinita” y luego bloquee para siempre en IO.
- Hecho 4: Con SSD y NVMe, el problema a menudo se desplaza de throughput a latencia cola—los picos del percentil 99.9 que causan colas y ráfagas repentinas.
- Hecho 5: Los sistemas de archivos con journaling (ext4, XFS) aún pueden producir patrones de IO que parecen en ráfaga, especialmente en cargas con metadatos intensivos.
- Hecho 6: Los entornos virtualizados pueden amplificar las tormentas: writeback a nivel host más writeback a nivel guest igual “caché doble”, y cada capa cree que está siendo útil.
- Hecho 7: La idea del kernel de “memoria disponible” es dinámica; los umbrales en ratio pueden subir y bajar durante reclaim, cambiando el comportamiento de writeback en medio de un incidente.
- Hecho 8: Fijar
dirty_writeback_centisecsa 0 no significa “sin writeback”, significa que el temporizador periódico de writeback está deshabilitado; el writeback sigue ocurriendo por otros desencadenantes. - Hecho 9: Muchos incidentes de “el disco es lento” son en realidad incidentes de “la cola está saturada”: tu disco puede ser rápido, pero le estás alimentando ráfagas patológicas.
Guía de diagnóstico rápido (verifica 1/2/3)
Esta es la versión para estar de guardia. No estás aquí para filosofar. Estás aquí para encontrar el cuello de botella en cinco minutos y decidir si ajustar vm.dirty ayuda.
Primero: confirma que es writeback, no IO aleatorio
- Revisa niveles de páginas sucias y actividad de writeback.
- Verifica si tareas están bloqueadas en estado
D. - Comprueba si iowait y la profundidad de cola del disco se disparan durante la congelación.
Segundo: identifica el dispositivo y la fuente de presión
- ¿Qué dispositivo de bloque está saturado? (NVMe? RAID? bloque de red?)
- ¿La carga son escrituras de archivos, commits de journal o swap/reclaim forzando writeback?
- ¿Esto ocurre dentro de una VM o host de contenedores con caché doble?
Tercero: elige la mitigación segura mínima
- Reduce los límites dirty (usa bytes) para iniciar writeback antes y evitar grandes ráfagas de flush.
- Opcionalmente acorta la expiración dirty para que los datos sucios antiguos no se acumulen.
- Valida con métricas de latencia y cola; revierte si el throughput se colapsa.
Tareas prácticas: 14 comprobaciones con comandos, salidas y decisiones
Estas son tareas reales que puedes ejecutar en Debian 13 durante un incidente o en una ventana tranquila. Cada una incluye: comando, salida de ejemplo, qué significa y qué decisión tomar.
Task 1: Inspect current dirty tunables
cr0x@server:~$ sysctl vm.dirty_ratio vm.dirty_background_ratio vm.dirty_bytes vm.dirty_background_bytes vm.dirty_expire_centisecs vm.dirty_writeback_centisecs
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_background_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500
Significado: Este host usa umbrales basados en ratio (bytes en 0). El writeback en segundo plano comienza al 10% de dirty, y los escritores se limitan al 20% dirty.
Decisión: En hosts con mucha memoria, 20% puede ser enorme. Si hay tormentas, planea cambiar a umbrales basados en bytes.
Task 2: Confirm the machine’s memory scale (ratios are relative)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 256Gi 92Gi 11Gi 2.0Gi 154Gi 150Gi
Swap: 8.0Gi 0.0Gi 8.0Gi
Significado: Con 256 GiB de RAM, un límite dirty del 20% puede traducirse en decenas de gigabytes de datos sucios. Eso es un vaciado grande.
Decisión: Prefiere bytes. Por ejemplo, limita dirty a unos pocos GiB a menos que tengas una razón sólida para no hacerlo.
Task 3: Watch dirty and writeback pages in real time
cr0x@server:~$ awk '/Dirty:|Writeback:|MemAvailable:|Cached:|Buffers:/{print}' /proc/meminfo
MemAvailable: 157392112 kB
Cached: 132884944 kB
Buffers: 126764 kB
Dirty: 6248120 kB
Writeback: 184320 kB
Significado: ~6.2 GiB sucios y algo de writeback activo. Durante una tormenta, Dirty puede subir rápidamente y luego Writeback se dispara cuando comienza el vaciado.
Decisión: Si Dirty crece a varios GB y Writeback va rezagado, tu umbral de fondo es demasiado alto o el almacenamiento no puede seguir el ritmo.
Task 4: Verify blocked tasks and IO wait during the freeze
cr0x@server:~$ top -b -n1 | head -n 20
top - 11:08:21 up 14 days, 3:52, 1 user, load average: 18.42, 16.77, 10.91
Tasks: 612 total, 4 running, 58 sleeping, 0 stopped, 9 zombie
%Cpu(s): 5.3 us, 2.1 sy, 0.0 ni, 33.7 id, 58.6 wa, 0.0 hi, 0.3 si, 0.0 st
MiB Mem : 262144.0 total, 11540.0 free, 94512.0 used, 156092.0 buff/cache
MiB Swap: 8192.0 total, 8192.0 free, 0.0 used. 157000.0 avail Mem
Significado: 58.6% iowait es saturación clásica. El load average sube porque las tareas esperan, no porque estés “CPU bound”.
Decisión: Continúa con el diagnóstico de la ruta de IO. El ajuste de dirty ayuda cuando la espera se correlaciona con writeback masivo.
Task 5: Identify the busiest block device and queue pressure
cr0x@server:~$ iostat -x 1 3
Linux 6.12.0 (server) 12/30/2025 _x86_64_ (64 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
4.92 0.00 2.18 56.10 0.00 36.80
Device r/s w/s rKB/s wKB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme0n1 8.0 1900.0 256.0 78000.0 82.4 32.5 17.3 2.1 17.4 0.5 99.2
Significado: %util cerca del 100% y avgqu-sz grande significa que el dispositivo está saturado. La latencia de escritura (w_await) está elevada.
Decisión: Si esto se alinea con la acumulación de dirty, reduce los umbrales dirty. Si no, encuentra la fuente de IO (journal, swap, lecturas aleatorias, etc.).
Task 6: Check pressure stall information (PSI) for IO
cr0x@server:~$ cat /proc/pressure/io
some avg10=12.43 avg60=8.11 avg300=3.21 total=91823354
full avg10=7.02 avg60=4.88 avg300=1.94 total=51299210
Significado: full indica tiempo en el que las tareas están completamente bloqueadas por IO. Si sube durante la tormenta, no es sutil.
Decisión: Un full alto respalda el caso de suavizar el writeback; estás viendo bloqueos a nivel sistema.
Task 7: Observe writeback and throttling counters
cr0x@server:~$ egrep 'dirty|writeback|balance_dirty' /proc/vmstat
nr_dirty 1605402
nr_writeback 48812
nr_writeback_temp 0
nr_dirtied 981234567
nr_written 979998123
balance_dirty_pages 734520
dirty_background_threshold 786432
dirty_threshold 1572864
Significado: balance_dirty_pages incrementa cuando las tareas son restringidas para mantener las páginas sucias bajo control. Los umbrales se muestran en páginas.
Decisión: Si ves nr_dirty enorme y picos súbitos en balance_dirty_pages, estás en territorio de ráfagas y throttling. Ajusta para empezar antes y evitar acantilados.
Task 8: Identify which processes are writing
cr0x@server:~$ pidstat -d 1 5
Linux 6.12.0 (server) 12/30/2025 _x86_64_ (64 CPU)
11:10:01 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
11:10:02 0 2143 0.00 32100.00 31500.00 12 rsyslogd
11:10:02 105 18721 0.00 98000.00 96500.00 88 postgres
11:10:02 0 291002 0.00 14000.00 13900.00 6 backup-agent
Significado: kB_ccwr/s muestra bytes de escritura cancelados—datos que se ensuciaron pero luego fueron truncados o sobrescritos antes del writeback. Valores altos pueden indicar churn.
Decisión: Si un proceso domina las escrituras, también puedes arreglar la carga (batching, cadencia de fsync, rotación de logs) en lugar de solo ajustar parámetros del kernel.
Task 9: Check filesystem and mount options
cr0x@server:~$ findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS /var/lib/postgresql
/dev/nvme0n1p3 /var/lib/postgresql ext4 rw,relatime,errors=remount-ro,data=ordered
Significado: ext4 data=ordered es el predeterminado y sensato; hace journaling de metadatos y asegura que los bloques de datos se escriban antes de que los metadatos apunten a ellos.
Decisión: No cambies los modos de journaling durante un incidente. Si tienes opciones de montaje exóticas, documenta y reevalúa más tarde.
Task 10: Check if you’re accidentally using writeback caching without power-loss protection
cr0x@server:~$ lsblk -d -o NAME,MODEL,ROTA,TRAN,TYPE,SIZE
NAME MODEL ROTA TRAN TYPE SIZE
nvme0n1 Samsung SSD 980 PRO 0 nvme disk 1.8T
cr0x@server:~$ sudo nvme id-ctrl /dev/nvme0n1 | egrep 'vwc|oncs'
vwc : 0x01
oncs : 0x001f
Significado: vwc indica presencia de caché de escritura volátil. Esto no es automáticamente malo, pero aumenta la importancia de barreras/comportamiento de flush y la protección contra pérdida de alimentación.
Decisión: Si la plataforma carece de protección contra pérdida de energía, mantén límites dirty conservadores. No puedes “sysctl” la física.
Task 11: Confirm the IO scheduler (and avoid cargo culting)
cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq
Significado: NVMe suele usar none por defecto; eso puede ser correcto. Los planificadores pueden afectar la latencia bajo contención.
Decisión: No trates la selección del planificador como la primera línea de defensa para tormentas de writeback. Afina primero los umbrales dirty; cambia planificadores solo con medición.
Task 12: Measure fsync/commit pressure (ext4 example)
cr0x@server:~$ ps -eLo pid,comm,wchan:30 | egrep 'jbd2|fsync|fdatasync' | head
612 jbd2/nvme0n1p3 jbd2_journal_commit_transaction
18721 postgres do_fsync
18804 postgres do_fsync
Significado: Si muchos hilos esperan en rutas de fsync mientras las páginas sucias son altas, tu carga está forzando puntos de durabilidad bajo writeback pesado.
Decisión: Considera reducir límites dirty (disminuir background/dirty bytes) y revisa el comportamiento de fsync y parámetros de checkpoint a nivel de aplicación.
Task 13: Apply a temporary, safer writeback profile (bytes-based)
cr0x@server:~$ sudo sysctl -w vm.dirty_background_bytes=$((512*1024*1024)) vm.dirty_bytes=$((2*1024*1024*1024))
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648
Significado: El writeback en segundo plano comienza alrededor de 512 MiB sucios, y el throttling comienza alrededor de 2 GiB sucios.
Decisión: Este es un primer ajuste sensato para muchos servidores. Si las tormentas se reducen y la latencia mejora, hazlo persistente y refina.
Task 14: Validate that storms shrink (dirty levels + IO await)
cr0x@server:~$ watch -n1 'awk "/Dirty:|Writeback:/{print}" /proc/meminfo; iostat -x 1 1 | tail -n +7'
Every 1.0s: awk "/Dirty:|Writeback:/{print}" /proc/meminfo; iostat -x 1 1 | tail -n +7
Dirty: 612480 kB
Writeback: 98304 kB
nvme0n1 6.0 820.0 192.0 31000.0 78.3 6.2 3.8 1.9 3.8 0.4 71.0
Significado: Dirty se mantiene por debajo de ~600 MiB, el writeback está activo pero no explosivo, await y la cola son más bajos, y %util tiene margen.
Decisión: Si esto se mantiene bajo carga máxima, has domado la tormenta. Si el throughput cae demasiado, aumenta dirty_bytes con cuidado (no ratios).
Estrategia de ajuste segura: reducir tormentas sin apostar la empresa
El ajuste de writeback es engañosamente fácil de hacer mal. La gente ve un post, pone a la fuerza vm.dirty_ratio=80, y luego se pregunta por qué un apagón convirtió la cola en una escena del crimen.
Aquí está la estrategia que funciona en producción real:
1) Prefiere umbrales basados en bytes en servidores modernos
Los ratios escalan con la memoria. Suena bien hasta que tu “20% dirty” se vuelve 30–50 GiB en un host grande. Si tu almacenamiento puede vaciar eso rápidamente y de forma consistente, no estarías leyendo esto.
Recomendación: Establece:
vm.dirty_background_bytesa 256–1024 MiBvm.dirty_bytesa 1–8 GiB dependiendo del almacenamiento y la carga
Comienza conservador. Aumenta solo si tienes dolor medido en throughput.
2) Mantén el umbral de fondo significativamente por debajo del límite dirty
Si los umbrales de background y dirty están demasiado cerca, no obtienes “writeback suave”, obtienes “writeback que comienza tarde y luego throttlea de inmediato”. Eso se siente como una congelación.
Regla práctica: background a 1/4 a 1/2 del límite dirty. Ejemplo: 512 MiB background, 2 GiB dirty.
3) Acorta la expiración dirty si tu carga genera dirt de larga vida
vm.dirty_expire_centisecs controla cuánto tiempo los datos sucios pueden permanecer antes de considerarse “lo suficientemente viejos” para ser volcados. Los valores por defecto suelen significar “hasta decenas de segundos”. Eso puede estar bien. También puede permitir una acumulación lenta que se convierte en tormenta cuando golpea la presión.
Recomendación: Si ves “acumulación silenciosa y luego flush repentino”, prueba reducir la expiración moderadamente (por ejemplo, de 30s a 10–15s). No la pongas a 1s y te sorprendas cuando cambie el throughput.
4) No deshabilites el writeback periódico a menos que entiendas las consecuencias
vm.dirty_writeback_centisecs controla los wakeups periódicos para el writeback en segundo plano. Ponerlo a 0 cambia la dinámica y puede desplazar el vaciado a desencadenantes más reactivos (reclaim, sync, rutas intensivas en fsync). Eso no es “más eficiente”. Eso es “más caótico”.
5) Recuerda qué estás optimizando: latencia cola
No intentas ganar un benchmark. Intentas mantener la latencia p99 manejable mientras sigues escribiendo suficientes datos. Límites dirty más bajos suavizan el writeback y reducen el tamaño máximo de la ráfaga. Ese es el objetivo.
Broma #2: Si ajustas ratios dirty “porque se siente más rápido”, has reinventado las pruebas de rendimiento—malamente.
6) Hazlo persistente, revisado y reversible
Los sysctls temporales arreglan incidentes. Los sysctls persistentes previenen repeticiones. Pero solo si se despliegan como cualquier otro cambio de producción: revisado por pares, documentado y desplegado gradualmente.
cr0x@server:~$ sudo tee /etc/sysctl.d/99-dirty-writeback.conf >/dev/null <<'EOF'
# Reduce writeback storms by starting writeback earlier and capping dirty cache.
# Bytes-based thresholds are predictable across RAM sizes.
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648
vm.dirty_expire_centisecs = 1500
vm.dirty_writeback_centisecs = 500
EOF
cr0x@server:~$ sudo sysctl --system
* Applying /etc/sysctl.d/99-dirty-writeback.conf ...
vm.dirty_background_bytes = 536870912
vm.dirty_bytes = 2147483648
vm.dirty_expire_centisecs = 1500
vm.dirty_writeback_centisecs = 500
Significado: Has aplicado un perfil controlado: writeback en fondo más temprano, caché sucia máxima más pequeña, expiración ligeramente más rápida.
Decisión: Lánzalo en un host canario primero, luego en una porción de la flota. Observa p99 y la cola de IO.
Perfiles sugeridos (VMs, BD, servidores de archivos, NVMe)
Estos son puntos de partida, no evangelio. Los valores correctos dependen del rendimiento de escritura del almacenamiento, la tolerancia a la latencia IO y cuán ráfaga son tus escritores.
Profile A: VM general o servidor de aplicaciones (sensibles a latencia)
dirty_background_bytes = 256–512 MiBdirty_bytes = 1–2 GiBdirty_expire_centisecs = 1500–3000(15–30s)
Usa esto cuando te importe más la capacidad de respuesta que el throughput de streaming.
Profile B: Host de base de datos (puntos de durabilidad + escrituras constantes)
dirty_background_bytes = 512 MiB–1 GiBdirty_bytes = 2–4 GiBdirty_expire_centisecs = 1000–2000(10–20s)
Las bases de datos suelen gestionar sus propios flush/checkpoint y cuidan la latencia de fsync. Un tamaño de tormenta más pequeño suele ser beneficioso.
Profile C: Servidor de archivos / destino de backup (orientado a throughput, sin tormentas)
dirty_background_bytes = 1–2 GiBdirty_bytes = 4–8 GiBdirty_expire_centisecs = 3000(30s)
Esto es para ingestión secuencial donde los usuarios toleran latencia algo mayor pero no congelaciones totales del host.
Profile D: RAID NVMe o SSD local muy rápido (evita “demasiado optimismo”)
Los dispositivos rápidos pueden vaciar rápido, lo que te tienta a subir límites dirty. La trampa es que la cola aún crea picos, y el writeback de fondo puede quedarse atrás cuando los patrones de metadatos/journal se vuelven raros.
- Comienza con Profile A o B de todos modos.
- Aumenta solo después de medir comportamiento sostenido de escritura y latencia p99.
Tres mini-historias corporativas desde las trincheras del writeback
Mini-historia 1: El incidente causado por una suposición equivocada (la historia “la RAM es gratis, ¿no?”)
Una empresa SaaS mediana migró un servicio analítico intensivo por lotes de nodos de 64 GB a nodos nuevos de 256 GB. El almacenamiento permaneció de la misma clase: SSD decentes detrás de un controlador, suficientemente bueno durante años. Su suposición fue simple: más RAM significa más caché, que significa menos accesos a disco, que significa trabajos más rápidos.
El primer lunes después del despliegue, el trabajo de ingestión diaria terminó más rápido—hasta que no. A mitad de la ejecución, la latencia de la API se disparó. Las sesiones SSH tartamudeaban. El load average alcanzó números que hicieron que la gráfica pareciera un skyline. El equipo inicialmente culpó a un vecino ruidoso en la capa de virtualización porque el uso de CPU era bajo y el load alto. Lectura clásica equivocada.
Cuando finalmente miraron /proc/meminfo y /proc/vmstat, fue obvio: la caché sucia había subido a decenas de gigabytes, luego el kernel limitó a los escritores y comenzó un flush agresivo. El almacenamiento podía escribir rápido, pero no “volcar 40 GB mientras atiende lecturas aleatorias y fsyncs” rápido. La carga no había cambiado; los umbrales por defecto sí.
Lo arreglaron con umbrales dirty basados en bytes y un disparador de fondo más bajo. El trabajo siguió siendo rápido, pero el sistema ya no se congelaba. La suposición equivocada no era “la caché ayuda”. Era “los valores por defecto escalan de forma segura con el hardware”. No lo hacen.
Mini-historia 2: La optimización que salió mal (el experimento “deja bufferizar más”)
Un equipo fintech tenía una canalización de logging de alto throughput que escribía grandes archivos append-only. Querían maximizar el throughput porque la conciliación nocturna dependía de la disponibilidad de logs. Alguien sugirió aumentar vm.dirty_ratio y vm.dirty_background_ratio para “dejar que Linux bufferice más y escriba en lotes más grandes”. En papel, eso puede mejorar la eficiencia de escritura secuencial.
Funcionó en una prueba rápida. El throughput mejoró. Todos asintieron. Luego la carga se topó con la realidad: rotación de logs, trabajos de compresión y un proceso de snapshot periódico. Esos introdujeron ráfagas de metadatos y operaciones sync. El sistema comenzó a experimentar pausas repentinas a la hora en punto, como un tren de pasajeros que se encuentra señales rojas inexplicables.
El problema más profundo: al subir los ratios dirty, aumentaron significativamente la huella sucia máxima. Bajo presión de rotación y snapshot, el writeback necesitó expulsar una montaña de datos sucios de una vez. Las tareas en primer plano—especialmente las que hacen fsync—comenzaron a quedarse atrás de ese flush. La canalización no solo se ralentizó; creó retrasos en cascada en sistemas dependientes.
La solución no fue “deshacer el rendimiento”. Fue elegir límites dirty más pequeños y comenzar el writeback en segundo plano antes, luego afinar la canalización para escribir de forma más consistente. Su “optimización” fue un cambio orientado solo a throughput en un sistema con dependencias sensibles a latencia. Así se falla una idea razonable.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día (canario + métricas + reversión)
Una compañía de medios tenía una flota de hosts Debian sirviendo cargas y transcodificaciones. Ya habían sido quemados antes por “arreglos de sysctl en una línea” que causaron una semana de regresiones sutiles. Así que trataron el ajuste del kernel como un despliegue de aplicación: canario, observar, expandir y mantener un plan de reversión.
Cuando aparecieron tormentas de writeback durante picos de subida, no aplicaron cambios a toda la flota. Eligieron un host representativo, aplicaron umbrales dirty basados en bytes y vigilaron dos cosas: IO PSI full y latencia de solicitudes en el borde de la aplicación. También observaron tasas de error, porque nada dice “ups” como timeouts que figuran como éxito.
El canario mejoró: menos stalls de IO, menor latencia cola y sin colapso de throughput. Lo desplegaron al 10% de la flota, luego al 50%. Un cluster con SSD SATA más antiguo vio una ligera caída de throughput, así que aumentaron dirty_bytes modestamente solo en esa clase de hardware. Sin drama, sin sala de crisis, sin heroísmos.
La práctica aburrida fue la ganadora: despliegue controlado más métricas significativas. Cuando ajustas writeback, la mejor herramienta no es un sysctl—es la moderación.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: congelaciones periódicas de todo el host con iowait alto
Causa raíz: La caché sucia se acumula hasta el límite dirty, luego los bursts de writeback saturan el almacenamiento y todo se throttlea.
Solución: Usa umbrales basados en bytes; baja dirty_bytes y dirty_background_bytes para que el writeback comience antes. Valida con Dirty/Writeback en /proc/meminfo y iostat -x la profundidad de cola.
2) Síntoma: el throughput está bien, pero la latencia p99 se dispara en cargas mixtas
Causa raíz: Efectos de encolamiento por bursts de writeback; journaling y puntos de fsync compiten con flushs masivos.
Solución: Reduce límites dirty (ráfagas más pequeñas). Revisa procesos intensivos en fsync y ajusta la cadencia de flush en la aplicación donde sea posible.
3) Síntoma: ajustar ratios “funciona” en una clase de host pero no en otra
Causa raíz: Los umbrales por ratio escalan con la memoria y con la contabilidad de memoria disponible del kernel, que varía según la carga y el rol del host.
Solución: Estandariza en dirty_bytes/dirty_background_bytes por clase de hardware.
4) Síntoma: después de bajar límites dirty, los escritores masivos se ralentizan dramáticamente
Causa raíz: Limitaste demasiado pronto respecto al throughput sostenido del disco; el writeback en segundo plano no puede seguir el ritmo, así que los escritores esperan constantemente.
Solución: Aumenta dirty_background_bytes ligeramente (inicia writeback antes pero da un poco más de margen) y/o aumenta dirty_bytes modestamente. Confirma throughput y latencia del disco; si el dispositivo es demasiado lento, el ajuste no creará ancho de banda.
5) Síntoma: “sync” o snapshots causan stalls de varios minutos
Causa raíz: Un enorme backlog dirty se encuentra con un trigger de flush forzado (sync, snapshot, commit del sistema de archivos), causando una avalancha de escrituras y actividad de journal.
Solución: Mantén el backlog dirty pequeño mediante umbrales en bytes. Programa snapshots fuera de picos. Asegura que tu herramienta de snapshots no fuerce sync global innecesariamente.
6) Síntoma: las tormentas ocurren principalmente dentro de VMs, no en metal desnudo
Causa raíz: Caché doble e interacciones de writeback entre guest y hypervisor. Cada capa hace buffering y luego vacía en ráfagas.
Solución: Usa dirty bytes conservadores dentro de guests. Si es posible, alinea las configuraciones de almacenamiento del hipervisor y evita buffering extremo en ambas capas. Mide en guest y host.
Listas de verificación / plan paso a paso
Paso a paso: del incidente a la configuración estable
- Confirma que es writeback. Revisa Dirty/Writeback en
/proc/meminfo, IO PSI y la cola coniostat. - Identifica el dispositivo caliente. Encuentra el dispositivo de bloque saturado y confirma que se mapea al sistema de archivos afectado.
- Captura una línea base corta. Guarda los sysctls actuales y una instantánea de 2–3 minutos de estadísticas de IO durante el evento.
- Aplica umbrales temporales en bytes. Comienza con 512 MiB background y 2 GiB dirty. Evita tocar opciones de montaje del sistema de archivos durante el incidente.
- Observa la forma. Dirty debería oscilar por debajo del límite; la profundidad de cola de IO debería bajar; la latencia debería suavizarse.
- Valida la salud de la aplicación. p99, tasas de error y latencia de commits DB si aplica.
- Persiste el cambio. Usa
/etc/sysctl.d/con comentarios que expliquen por qué. - Rollout canario. Un host → una pequeña porción → la flota, con dashboards que incluyan IO PSI y disk await.
- Refina por clase. Discos antiguos y dispositivos de bloque en red pueden necesitar límites distintos que NVMe local.
- Escribe una nota en el runbook. “Si ves A/B/C, comprueba estos valores y estas gráficas.” El tú del futuro es un stakeholder.
Lista de seguridad (la lista “no crees nuevos incidentes”)
- No aumentes límites dirty durante un incidente a menos que estés absolutamente seguro de que solo estás limitado por throughput y no por latencia.
- No mezcles ratio y bytes “porque ambos parecen relevantes”. Elige bytes para previsibilidad.
- No deshabilites timers de writeback como primera medida.
- No cambies modos de journaling o ajustes de barreras para arreglar tormentas. Eso no es tuning; es apostar.
- Sí, mantén límites dirty conservadores en sistemas sin protección contra pérdida de energía.
- Sí, prueba en el mismo patrón de carga que desencadena la tormenta (ventana de batch, backup, compactación).
Preguntas frecuentes (FAQ)
1) ¿Son las tormentas de writeback un bug de Debian 13?
Normalmente no. Son un desajuste entre valores por defecto, el tamaño de tu RAM y la ráfaga de tu carga. Debian 13 solo hace que sea más fácil notarlo porque pilas modernas sacan a la luz problemas de latencia cola con más claridad.
2) ¿Debo usar vm.dirty_ratio o vm.dirty_bytes?
Usa bytes en servidores donde la previsibilidad importa (la mayoría). Los ratios pueden ser aceptables en máquinas pequeñas y uniformes, pero escalan mal con el crecimiento de RAM.
3) ¿Bajar los límites dirty aumenta la seguridad de los datos?
Reduce cuánto dato de archivo sin escribir puede estar en RAM, así la exposición ante pérdida de energía disminuye. No reemplaza prácticas de durabilidad adecuadas (journaling, semántica correcta de flush, UPS/PLP en el almacenamiento).
4) ¿Puedo poner límites dirty extremadamente bajos para eliminar tormentas?
Puedes, pero podrías convertir las tormentas en throttling permanente. La meta es writeback continuo y más pequeño—no forzar a cada escritor a comportarse como si hiciera IO síncrono todo el tiempo.
5) ¿Qué pasa con bases de datos que usan O_DIRECT o IO directo?
El IO directo evita la caché de páginas para archivos de datos, lo que reduce la presión de páginas sucias de esa carga. Pero las bases de datos todavía escriben logs, metadatos y otros archivos a través de la caché, y el resto del sistema sigue usando page cache. El ajuste dirty puede seguir importando.
6) ¿Debería ajustar vm.swappiness en su lugar?
Swappiness afecta el comportamiento de reclaim y el uso de swap; puede influir cuándo se dispara writeback bajo presión de memoria, pero no es la herramienta principal para tormentas de writeback. Arregla los umbrales dirty primero, luego mira reclaim si aún ves thrashing.
7) ¿Por qué las tormentas ocurren en momentos “aleatorios”?
A menudo son disparadas por un evento periódico: rotación de logs, backups, compactación, snapshots o presión de memoria por una nueva carga. Correlaciona el tiempo con cron/systemd timers y horarios de aplicación.
8) ¿Cambiar el scheduler IO es mejor que ajustar dirty?
A veces los planificadores ayudan la latencia cola bajo contención, pero no arreglan la causa raíz de “demasiados datos sucios volcados demasiado tarde”. Ajustar schedulers sin tocar writeback es pulir la parte equivocada de la máquina.
9) ¿Cómo sé que no solo enmascaré el problema?
Si el dispositivo sigue al 100% de util y la cola sigue profunda, no resolviste el cuello de botella; solo cambiaste cuándo duele. Una buena solución reduce el tiempo de stall (IO PSI), reduce la profundidad de cola y mejora la latencia de la aplicación sin explotar las tasas de error.
Siguientes pasos que puedes hacer hoy
Si estás viendo tormentas de writeback en Debian 13, no empieces por superstición. Empieza por la evidencia: niveles Dirty/Writeback, IO PSI y profundidad de cola del disco. Luego haz un cambio disciplinado: pasa de umbrales por ratio a topes por bytes que encajen con la realidad de tu almacenamiento.
Haz esto a continuación:
- Ejecuta las comprobaciones de diagnóstico rápido y captura una línea base durante una tormenta.
- Aplica un perfil temporal:
dirty_background_bytes=512MiB,dirty_bytes=2GiB, y opcionalmentedirty_expire_centisecs=1500. - Confirma que las tormentas se reducen: Dirty se mantiene acotado, la cola de IO baja, p99 mejora.
- Persiste la configuración en
/etc/sysctl.d/con comentarios, haz canario y luego despliega.
No necesitas eliminar el writeback. Necesitas evitar que aparezca todo de golpe, como una factura impaga con intereses.