ZFS Prefetch: la configuración oculta detrás de la caída de la caché

¿Te fue útil?

Prefetch es el tipo de característica que solo notas cuando te traiciona. La mayoría de los días, convierte silenciosamente lecturas secuenciales en E/S suaves y eficientes. Y luego, un día, tu ARC se vuelve una puerta giratoria, la tasa de aciertos se desploma, la latencia se dispara y alguien pronuncia la frase más peligrosa del almacenamiento: “Pero no cambió nada”.

Esta es una guía de campo sobre esa traición: qué hace realmente el prefetch de ZFS, cómo interactúa con ARC y L2ARC, cómo puede crear thrash en la caché y cómo arreglarlo sin convertir tu almacenamiento en una feria de ciencias. Lo mantendré anclado en lo que puedes medir en sistemas de producción y en lo que puedes cambiar con seguridad cuando la gente está esperando.

Prefetch en palabras sencillas (y por qué puede perjudicar)

El prefetch de ZFS es un mecanismo adaptativo de “lectura anticipada”. Cuando ZFS detecta que estás leyendo datos de archivos de forma secuencial, comienza a traer los siguientes fragmentos antes de que los solicites. Esa es la idea central: ocultar la latencia del disco manteniendo los bloques siguientes listos en memoria.

Cuando funciona, es magia: las lecturas de flujo se sirven desde ARC a velocidad de memoria. Cuando falla, se convierte en una forma especialmente cara de optimismo: arrastra muchos datos al ARC que nunca usarás, expulsando los datos que sí vas a usar. Eso es thrash de caché.

El thrash de caché causado por prefetch suele aparecer en tres situaciones generales:

  • Escaneos grandes “mayormente secuenciales” que no se repiten (consultas analíticas, backups, escaneos antivirus, indexación de medios, envío de logs). Prefetch llena el ARC con datos usados una sola vez.
  • Lecturas secuenciales a través de muchos archivos en paralelo (múltiples máquinas virtuales, varios trabajadores, consumidores paralelos). Cada flujo parece secuencial por sí solo, pero colectivamente se convierten en una manguera de incendios.
  • Workloads que parecen secuenciales pero no son útiles para la caché (p. ej., leer archivos enormes una sola vez, o leer bloques comprimidos/encriptados donde la CPU se convierte en el cuello de botella y la permanencia en ARC solo quema RAM).

Dos chistes, como prometí, y luego nos ponemos serios:

Chiste #1: Prefetch es como un becario que empieza a imprimir los correos de mañana “para ahorrar tiempo”. Gran iniciativa, juicio catastrófico.

Cómo funciona el prefetch de ZFS internamente

Mapeemos el territorio. En OpenZFS, una lectura normal viaja a través del DMU (Data Management Unit), que encuentra los bloques (punteros a bloques, bloques indirectos, dnodes), y luego emite E/S para los bloques de datos. ARC cachea tanto metadata como datos. ZFS prefetch añade un comportamiento extra: cuando ZFS detecta un patrón de acceso secuencial, emitirá lecturas adicionales para bloques futuros.

ARC, MRU/MFU y por qué el prefetch cambia la expulsión

ARC no es un simple LRU. Es adaptativo: equilibra entre “recientemente usado” (MRU) y “frecuentemente usado” (MFU). En un modelo mental simplificado:

  • MRU: “cosas nuevas” que tocaste recientemente (a menudo lecturas de una sola vez).
  • MFU: “cosas calientes” a las que vuelves con frecuencia.

Prefetch tiende a meter bloques en ARC que la aplicación aún no ha demandado. Dependiendo de detalles de implementación y del workload, esos bloques prefetchados pueden inflar el lado “reciente”, empujando fuera la metadata útil y los datos del conjunto de trabajo. El resultado es una peor tasa de aciertos, más lecturas reales desde disco y, a veces, un lazo de retroalimentación desagradable: más misses causan más lecturas, más lecturas generan más prefetch, el prefetch expulsa más caché, y así sucesivamente.

Prefetch no es lo mismo que la caché de páginas del SO

En Linux, ZFS está fuera de la caché de páginas tradicional. Eso es una característica, no un bug: ZFS controla su lógica de caché. Pero también significa que no puedes asumir que los habituales controles de readahead de Linux te cuentan toda la historia. ZFS tiene su propio comportamiento de prefetch y parámetros de afinado, y las consecuencias caen directamente en la presión de memoria del ARC, no “solo” en la caché VFS.

La metadata importa: el prefetch puede expulsar lo aburrido

Las peores caídas de rendimiento que he visto no fueron causadas por “los datos son lentos”, sino por “falta metadata”. Cuando ARC pierde metadata (dnodes, bloques indirectos), cada operación se convierte en múltiples E/S: encontrar el dnode, leer los bloques indirectos, finalmente leer los datos. El prefetch que expulsa metadata puede convertir “una lectura” en “un pequeño desfile de lecturas”, y el desfile es muy educado pero muy lento.

L2ARC: no es una tarjeta de escape gratuita

L2ARC (caché SSD) se entiende frecuentemente como “ARC pero más grande”. No lo es. L2ARC se popula por expulsiones desde ARC; tiene su propia sobrecarga de metadata en RAM; e históricamente no persistía entre reinicios (las versiones más nuevas de OpenZFS admiten L2ARC persistente, pero aún no es magia). Si prefetch está metiendo basura en ARC, L2ARC puede convertirse en un museo de cosas que leíste una vez pero que nunca volverás a leer.

Los controles de configuración que escucharás

Diferentes plataformas exponen tunables ligeramente distintos (FreeBSD vs Linux/ versiones de OpenZFS), pero dos nombres aparecen repetidamente:

  • zfs_prefetch_disable: instrumento contundente; deshabilita el prefetch.
  • zfetch_max_distance / zfetch_max_streams: los límites de “cuánto” y “cuántos” para el comportamiento de prefetch (los nombres varían por versión).

No memorices los nombres. Memoriza la estrategia: medir, confirmar que el prefetch es la fuente de presión, luego restringirlo de la forma más estrecha que arregle el problema.

Por qué ocurre el thrash de caché: modos de fallo

Modo de fallo 1: lecturas secuenciales de una sola vez que parecen “útiles”

Los backups son el ejemplo clásico. Un backup lee datasets enteros secuencialmente, normalmente una vez al día. Prefetch avanzará con gusto, ARC se llenará con gusto, y tu conjunto de trabajo de producción será expulsado para dejar espacio a los bytes menos reutilizables del sistema.

Patrón de síntomas: empiezan los backups, la tasa de aciertos del ARC cae, la latencia sube y las consultas orientadas al cliente se ralentizan aunque los discos no estén saturados por ancho de banda—están saturados en IOPS aleatorios porque los misses de caché fuerzan más seeks.

Modo de fallo 2: flujos secuenciales paralelos

Un flujo secuencial es fácil: básicamente es una cinta transportadora. Veinte flujos secuenciales a través de veinte VMs es una rotonda bajo tormenta. Cada flujo dispara prefetch. El prefetch combinado trae mucha más información de la que ARC puede contener, y la expulsión se vuelve constante.

Modo de fallo 3: prefetch compite con metadata y bloques pequeños calientes

ARC necesita mantener las “tarjetas índice” de tu sistema de archivos: dnodes, bloques indirectos, estructuras de directorio. Prefetch tiende a inyectar grandes secuencias de datos de archivo. Si ARC es pequeño en relación con el conjunto de trabajo, el prefetch puede inclinar la balanza: empiezas a fallar en metadata que antes estaba residente, y de repente todo se vuelve lento, incluidas operaciones que no forman parte del escaneo secuencial.

Modo de fallo 4: contaminación de L2ARC y sobrecarga RAM

L2ARC suena como un cojín, pero cuesta RAM para su índice y no es gratis poblarlo. Si prefetch está inyectando datos desechables en ARC, los expulsarás a L2ARC, gastando E/S y RAM para preservar algo que nunca volverás a leer. Eso no es cachear; eso es acaparar.

Modo de fallo 5: compresión, cifrado y cuellos de botella de CPU

Si las lecturas están ligadas a CPU (descompresión, desencriptado, checksum), el prefetch puede seguir trayendo datos al ARC más rápido de lo que la CPU los consume, o traer datos que serán invalidados por otras necesidades del conjunto de trabajo. Verás CPU al máximo, ARC agitado y discos no aparentemente saturados—una tríada especialmente confusa en llamadas de incidentes.

Hechos y contexto histórico (6–10 puntos rápidos)

  1. ZFS fue diseñado para integridad end-to-end: checksums, copy-on-write, self-healing—las características de rendimiento como el prefetch siempre se apilaron sobre un diseño que prioriza la corrección.
  2. ARC se creó para superar el pensamiento LRU simple: se adapta entre recurrencia y frecuencia, por eso puede sobrevivir a cargas mixtas—hasta que le das demasiado “recency” vía prefetch.
  3. Los despliegues tempranos de ZFS solían tener mucha RAM: el comportamiento de prefetch que era inocuo con 256 GB de RAM se vuelve dramático en nodos ajustados a huellas de 64 GB.
  4. L2ARC históricamente no era persistente: los arranques fríos significaban caché fría, lo que animó a la gente a sobreajustar prefetch para “calentar” cosas, a veces creando churn autoinfligido.
  5. OpenZFS se dividió y se reconvergió: linaje Solaris, illumos, FreeBSD, Linux—los controles y valores por defecto de prefetch evolucionaron de forma distinta entre ecosistemas.
  6. El “recordsize” se volvió religión de rendimiento: afinar recordsize para workloads a menudo eclipsa los efectos de prefetch, pero el prefetch interactúa con él; los registros grandes amplifican el coste de lecturas especulativas.
  7. Los SSD cambiaron la forma del dolor: el prefetch se inventó en un mundo donde la latencia era cara; con NVMe, la penalidad de un miss puede ser menor, pero la contaminación de caché aún puede matar la latencia en la cola.
  8. La virtualización multiplicó los flujos secuenciales: un único pool ahora sirve a docenas de invitados; los algoritmos de prefetch que asumen “unos pocos flujos” pueden comportarse mal bajo patrones multi-tenant.
  9. La gente ama los interruptores: la existencia de zfs_prefetch_disable es prueba de que suficientes operadores sufrieron dolor real para justificar un gran interruptor rojo.

Tres mini-historias del mundo corporativo

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

El incidente empezó como un ticket de “lentitud menor”. Una app interna respaldada por base de datos—nada glamoroso—estaba de repente haciendo timeouts en horas punta. Las gráficas de almacenamiento parecían bien a primera vista: el ancho de banda no estaba saturado, los discos no estaban alborotados y la CPU no estaba al máximo. La conclusión favorita de todos apareció en el chat: “No puede ser almacenamiento”.

Pero la latencia contó otra historia. La latencia del percentil 99 subió, no la media. Ese es el sello de las cachés que mienten: la mayoría de las peticiones van bien, pero un subconjunto se cae del acantilado de la caché y toma el camino lento. La suposición equivocada fue sutil: el equipo creía que el trabajo de backup nocturno era “secuencial y por lo tanto amigable”.

En realidad, el trabajo de backup se ejecutó tarde y se solapó con las horas de negocio. Leyó datasets enormes en largas corridas secuenciales. Prefetch vio a un estudiante perfecto—lecturas predecibles—y avanzó agresivamente. ARC se llenó con datos de backup. La metadata y los bloques pequeños y frecuentemente accedidos para la app fueron expulsados. La app no necesitaba alto throughput; necesitaba aciertos de baja latencia en un pequeño conjunto de trabajo. El backup necesitaba throughput pero no necesitaba caché en absoluto porque no iba a volver a leer los mismos bloques pronto.

Arreglarlo no fue heroico. El equipo primero confirmó que la caída de la tasa de aciertos del ARC se correlacionaba con la ventana de backup. Luego limitaron el backup y ajustaron la programación. Finalmente, restringieron el comportamiento de prefetch para ese nodo (no globalmente en la flota) y redimensionaron ARC para proteger metadata. La lección no fue “prefetch es malo”. La lección fue “secuencial no siempre significa cacheable”.

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

Un equipo de plataforma quería acelerar trabajos analíticos en un pool ZFS compartido. Observaban escaneos de tablas grandes y concluyeron que la mejor medida era “ayudar” a ZFS aumentando la paralelización de lectura: más workers, chunks más grandes, escaneos más agresivos. En el papel, parecía una victoria de throughput.

Funcionó—en un clúster vacío. En producción, colisionó con todo lo demás. Cada worker creó un flujo secuencial ordenado, así que prefetch se activó para cada uno. De repente el pool tenía una docena de streams de prefetch compitiendo por ARC. La tasa de expulsión de ARC subió hasta que parecía una máquina tragaperras. El tráfico de escritura hacia L2ARC también aumentó, convirtiendo los SSD de caché en dispositivos de journaling involuntarios para datos especulativos.

Y luego llegó la guinda: las consultas analíticas sólo fueron un poco más rápidas, pero el resto de la compañía se volvió más lenta. Los jobs de CI hicieron timeout. Las tormentas de arranque de VMs tardaron más. Incluso los listados de directorios y las instalaciones de paquetes se sintieron “pegajosas” porque faltaba metadata en la caché. La optimización era real pero externalizó el coste a todos los vecinos.

El rollback no fue “desactivar prefetch en todas partes”. El equipo redujo la concurrencia, introdujo aislamiento de recursos (clase de pool separada / tier de almacenamiento separado) y añadió salvaguardas: los nodos analíticos tuvieron su propio perfil de afinado, incluyendo límites de prefetch más estrictos. El revés no fue porque la idea fuera estúpida; fue porque los sistemas compartidos castigan el throughput egoísta.

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

Un incidente de almacenamiento una vez no llegó a ser un desastre puramente porque alguien fue aburrido. El equipo tenía una rutina: antes de cualquier afinado de rendimiento, capturaban un pequeño paquete de evidencias—estadísticas de ARC, estadísticas de E/S de ZFS, salud del pool y un snapshot de 60 segundos de la distribución de latencia. Tomaba cinco minutos y se consideraba “un proceso molesto”.

Una tarde, un ingeniero propuso cambiar zfs_prefetch_disable=1 porque “el prefetch siempre perjudica a las bases de datos”. Esa creencia es común, ocasionalmente cierta y peligrosamente sobregeneralizada. La rutina aburrida proporcionó el contraejemplo: ARC era mayoritariamente metadata, la tasa de aciertos era alta y el dolor dominante eran en realidad escrituras sincrónicas de una aplicación mal configurada. Las lecturas no eran el cuello de botella.

En lugar de alternar prefetch e introducir una nueva variable, arreglaron la ruta de escritura (comportamiento fsync de la aplicación, ajustes de sync del dataset alineados con requisitos de negocio). El outage terminó rápido y predeciblemente. Una semana después, hicieron un experimento controlado sobre prefetch y descubrieron que no era el villano en ese sistema.

Moral operativa: el hábito “aburrido” de recopilar las mismas estadísticas base cada vez convierte el tuning de folklore en ingeniería. Además, evita que ganes la discusión y pierdas el sistema.

Guion de diagnóstico rápido

Este es el orden que uso cuando alguien dice “ZFS está lento” y las gráficas están gritando en varios colores.

Primero: confirma la clase del síntoma (latencia vs throughput vs CPU)

  1. ¿Es latencia? Mira p95/p99 de la aplicación, luego mapea a la latencia de almacenamiento (dispositivo y capa ZFS). El thrash de caché suele aparecer como picos en la latencia de cola.
  2. ¿Es saturación de throughput? Si estás maximizando el ancho de banda secuencial y todo es secuencial, el prefetch puede estar bien.
  3. ¿Es CPU? CPU alta en threads del kernel haciendo checksums/compresión puede hacer que el “almacenamiento” parezca lento.

Segundo: revisa el comportamiento del ARC en 60 segundos

  1. Tendencia de la tasa de aciertos del ARC: ¿se desplomó cuando empezó la carga?
  2. Tamaño del ARC vs objetivo: ¿está ARC anclado al máximo con alta expulsión?
  3. Balance metadata vs datos: ¿la metadata está siendo desplazada?

Tercero: correlaciona con un escaneo secuencial

  1. Backups, scrubs, resilvers, lecturas de replicación, escaneos analíticos, antivirus, indexación de búsqueda, transcodificación de medios.
  2. Busca una marca temporal “un trabajo empezó” que coincida con el churn del ARC.

Cuarto: valida si el prefetch está realmente implicado

  1. Busca evidencia de read-ahead agresivo (alto throughput de lectura con baja reutilización de caché, alta expulsión).
  2. Prueba un cambio estrecho: reduce la distancia/streams de prefetch (o desactiva temporalmente prefetch) en un host, durante una ventana controlada, y mide.

Quinto: elige la mitigación menos peligrosa

  1. Limita o reprograma la carga de escaneo.
  2. Reduce la concurrencia (especialmente lectores secuenciales paralelos).
  3. Restringe el prefetch en vez de matarlo globalmente.
  4. Ajusta el tamaño del ARC para proteger metadata si la RAM lo permite.

Chiste #2: Desactivar prefetch en producción porque “podría ayudar” es como arreglar un traqueteo quitando la radio. El ruido para, claro, pero no has aprendido nada.

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

Estas tareas asumen Linux con OpenZFS instalado. Algunos comandos requieren paquetes (p. ej., zfsutils-linux) y privilegios root. El objetivo no es ejecutar todo siempre; es tener una caja de herramientas y saber qué te dice cada herramienta.

Tarea 1: Identificar pool y dataset básicos

cr0x@server:~$ zpool status
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 02:11:33 with 0 errors on Sun Dec 22 03:14:10 2025
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          mirror-0  ONLINE       0     0     0
            nvme0n1 ONLINE       0     0     0
            nvme1n1 ONLINE       0     0     0

errors: No known data errors

Interpretación: Si el pool está degradado/resilvering/scrubbing, las conclusiones de rendimiento son dudosas. El tuning de prefetch no arreglará un pool que está ocupado sanando sus datos.

Tarea 2: Ver propiedades del dataset que afectan patrones de lectura

cr0x@server:~$ zfs get -o name,property,value -s local,received recordsize,compression,primarycache,secondarycache,sync tank/data
NAME       PROPERTY       VALUE
tank/data  recordsize     128K
tank/data  compression    lz4
tank/data  primarycache   all
tank/data  secondarycache all
tank/data  sync           standard

Interpretación: recordsize y las políticas de caché importan. Recordsize grande + prefetch agresivo puede significar lecturas especulativas enormes. Si primarycache=metadata, los datos no permanecerán en ARC independientemente del prefetch.

Tarea 3: Comprobar el estado del control de prefetch

cr0x@server:~$ cat /sys/module/zfs/parameters/zfs_prefetch_disable
0

Interpretación: 0 significa que el prefetch está activado. No lo cambies aún; primero demuestra que es el culpable.

Tarea 4: Capturar un resumen rápido del ARC

cr0x@server:~$ arcstat 1 5
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:40:01  9234  1840     19   220   12  1620   88    95    5   48G   48G
12:40:02 10110  2912     28   240    8  2672   92   110    4   48G   48G
12:40:03  9988  3220     32   230    7  2990   93   105    3   48G   48G
12:40:04  9520  3011     31   210    7  2801   93   100    3   48G   48G
12:40:05  9655  3155     33   205    6  2950   94    99    3   48G   48G

Interpretación: Un miss% creciente durante un escaneo es una señal clásica de thrash. Si los misses son mayoritariamente pmis (prefetch misses), eso es sugerente, pero no definitivo. La clave es la tendencia y la correlación con el inicio de la carga.

Tarea 5: Comprobar los límites de tamaño del ARC (Linux)

cr0x@server:~$ cat /sys/module/zfs/parameters/zfs_arc_max
51539607552
cr0x@server:~$ cat /sys/module/zfs/parameters/zfs_arc_min
4294967296

Interpretación: ARC max ~48 GiB aquí. Si ARC es pequeño en relación con la carga de trabajo, prefetch puede churnearlo rápido. Si ARC es enorme y aún así hay thrash, el workload probablemente no tiene reutilización o hay demasiados streams.

Tarea 6: Vigilar la E/S de ZFS a nivel de pool

cr0x@server:~$ zpool iostat -v 1 5
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                         3.2T  10.8T   2400    110   780M   6.2M
  mirror-0                   3.2T  10.8T   2400    110   780M   6.2M
    nvme0n1                      -      -   1200     60   390M   3.1M
    nvme1n1                      -      -   1200     60   390M   3.1M
--------------------------  -----  -----  -----  -----  -----  -----

Interpretación: Si el ancho de banda de lectura es alto durante la degradación pero la aplicación no se beneficia, puede que estés leyendo por adelantado de forma inútil. Cruzar con misses de ARC y latencia de la app.

Tarea 7: Confirmar si un scrub/resilver está compitiendo

cr0x@server:~$ zpool status | sed -n '1,25p'
  pool: tank
 state: ONLINE
  scan: scrub in progress since Thu Dec 25 12:10:01 2025
        1.20T scanned at 6.9G/s, 210G issued at 1.2G/s, 3.20T total
        0B repaired, 6.56% done, 00:38:21 to go

Interpretación: Un scrub es también un lector secuencial, y puede disparar prefetch y competir por ARC. Si tu thrash coincide con un scrub, tienes un problema de programación primero.

Tarea 8: Identificar los principales lectores (a nivel de proceso)

cr0x@server:~$ sudo iotop -oPa
Total DISK READ: 820.12 M/s | Total DISK WRITE: 6.41 M/s
  PID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN  IO>    COMMAND
18277 be/4  backup    612.55 M/s   0.00 B/s  0.00 % 98.00 %  borg create ...
23110 be/4  postgres   84.12 M/s   2.40 M/s  0.00 % 12.00 %  postgres: checkpointer
 9442 be/4  root       32.90 M/s   0.00 B/s  0.00 %  4.00 %  zfs send ...

Interpretación: Si un proceso es un hog secuencial, a menudo puedes resolver el incidente sin tocar prefetch: limitarlo, moverlo, reprogramarlo.

Tarea 9: Verificar si el prefetch se desactiva/activa correctamente (temporal)

cr0x@server:~$ echo 1 | sudo tee /sys/module/zfs/parameters/zfs_prefetch_disable
1
cr0x@server:~$ cat /sys/module/zfs/parameters/zfs_prefetch_disable
1

Interpretación: Este es un cambio en tiempo de ejecución para experimentos rápidos. Trátalo como un interruptor de circuito: registra la hora, mide durante 5–15 minutos y reviértelo si perjudica el throughput secuencial. Hazlo persistente solo después de comprobar la ganancia y entender la compensación.

Tarea 10: Revertir el cambio limpiamente

cr0x@server:~$ echo 0 | sudo tee /sys/module/zfs/parameters/zfs_prefetch_disable
0

Interpretación: Siempre revierte en la misma ventana de incidente si los datos no soportan el cambio. Afinar producción sin rollback es apostar con mejor vocabulario.

Tarea 11: Comprobar presión de memoria que agrava la expulsión de ARC

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           128Gi        93Gi       2.1Gi       1.2Gi        33Gi        30Gi
Swap:            0B          0B          0B

Interpretación: Si “available” es bajo, ARC puede estar peleando con el resto del sistema. El prefetch bajo presión de memoria es más propenso a expulsar páginas útiles rápido.

Tarea 12: Examinar la descomposición de memoria del ARC (kstat)

cr0x@server:~$ sudo kstat -p zfs:0:arcstats:size zfs:0:arcstats:target_size zfs:0:arcstats:arc_meta_used zfs:0:arcstats:data_size
zfs:0:arcstats:size                51511234560
zfs:0:arcstats:target_size         51539607552
zfs:0:arcstats:arc_meta_used        8123456789
zfs:0:arcstats:data_size           43200000000

Interpretación: Si el uso de metadata colapsa durante un escaneo, has encontrado un mecanismo probable para “todo se vuelve lento”. Proteger la metadata (mediante dimensionado de ARC y control de cargas) a menudo restaura la latencia de cola.

Tarea 13: Inspeccionar el comportamiento de L2ARC (si está presente)

cr0x@server:~$ sudo kstat -p zfs:0:arcstats:l2_hits zfs:0:arcstats:l2_misses zfs:0:arcstats:l2_size
zfs:0:arcstats:l2_hits             182334
zfs:0:arcstats:l2_misses           992112
zfs:0:arcstats:l2_size             214748364800

Interpretación: Un L2ARC gigante con baja tasa de aciertos durante el thrash puede estar contaminado por datos prefetechados de una sola vez. Si falta L2ARC tampoco puede salvarte; es solo otra capa lenta detrás del ARC.

Tarea 14: Validar la alineación de recordsize para la carga (chequeo rápido)

cr0x@server:~$ zfs get recordsize tank/db tank/backup tank/vmstore
NAME        PROPERTY    VALUE   SOURCE
tank/db     recordsize  16K     local
tank/backup recordsize  1M      local
tank/vmstore recordsize 128K    local

Interpretación: Si tu base de datos está en registros de 1M, el prefetch va a traer especulativamente bloques grandes que pueden incluir páginas sin usar. Si tus backups están en registros pequeños, harás más E/S de la necesaria. Pon lo básico en orden antes de culpar al prefetch.

Tarea 15: Medir la latencia real en el dispositivo de bloque

cr0x@server:~$ iostat -x 1 5
Device            r/s   rkB/s  rrqm/s  %rrqm r_await rareq-sz   w/s   wkB/s w_await aqu-sz  %util
nvme0n1         1180  402000     0.0   0.00    4.10   340.7      60    3200   0.95   3.10  74.2
nvme1n1         1195  401000     0.0   0.00    4.05   335.6      62    3400   1.01   3.05  75.0

Interpretación: Si r_await del dispositivo salta cuando miss% del ARC salta, estás yendo al disco. Si la latencia del dispositivo es estable pero la app está lenta, puede que estés limitado por CPU o bloqueos/colas por encima de la capa de dispositivo.

Tarea 16: Hacer persistente la configuración de prefetch (solo tras pruebas)

cr0x@server:~$ echo "options zfs zfs_prefetch_disable=1" | sudo tee /etc/modprobe.d/zfs-prefetch.conf
options zfs zfs_prefetch_disable=1
cr0x@server:~$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.8.0-40-generic

Interpretación: Los cambios persistentes deben gestionarse en el proceso de cambios. Si no puedes articular la desventaja (lecturas secuenciales más lentas) y la ruta de rollback (eliminar el archivo, reconstruir initramfs, reiniciar), no estás listo para hacerlo persistente.

Errores comunes, síntomas y correcciones

Error 1: “La tasa de aciertos del ARC es baja, así que añade L2ARC”

Síntoma: Añades una gran SSD de caché, la tasa de aciertos apenas mejora y ahora el sistema tiene mayor amplificación de escritura y menos RAM disponible.

Por qué ocurre: El workload tiene poca reutilización; el prefetch está contaminando ARC; L2ARC solo almacena la contaminación con sobrecarga adicional.

Corrección: Primero reduce las lecturas especulativas (límites de prefetch o throttling de la carga). Valida la reutilización. Solo entonces considera L2ARC, dimensionado y monitorizado para mejora real de aciertos.

Error 2: Desactivar prefetch globalmente porque “las bases de datos lo odian”

Síntoma: Los reports y jobs por lotes se vuelven más lentos; las ventanas de backup se alargan; los resilvers tardan más; los usuarios se quejan de “exportaciones lentas”.

Por qué ocurre: Algunos workloads se benefician enormemente del prefetch. Desactivar globalmente penaliza a todos los lectores secuenciales, incluidos los legítimos.

Corrección: Prefiere contener el workload ofensivo (programación/limitación) o usar tuning dirigido por rol por host/pool. Si debes desactivar, documenta y mide el impacto en tareas secuenciales.

Error 3: Confundir “alto ancho de banda” con “buen rendimiento”

Síntoma: El almacenamiento está leyendo 800 MB/s, pero la aplicación está lenta.

Por qué ocurre: Estás transmitiendo datos al ARC y los expulsas de inmediato (o leyendo por adelantado más allá de lo usado). Esa es actividad, no progreso.

Corrección: Revisa miss%, expulsiones y tiempos de completado a nivel de aplicación. Si el throughput de la app no escala con el ancho de banda del pool, probablemente estás haciendo lecturas especulativas o sufriendo misses de metadata.

Error 4: Ignorar la residencia de metadata

Síntoma: “Todo” se vuelve más lento durante un escaneo: listados, lecturas pequeñas, respuesta de VMs.

Por qué ocurre: La metadata fue expulsada; las operaciones ahora requieren lecturas adicionales para encontrar datos.

Corrección: Asegura que ARC esté dimensionado apropiadamente; evita ejecutar escaneos que contaminen la caché durante las horas punta; considera cambios en la política de caché (primarycache) solo cuando entiendas las consecuencias.

Error 5: Afinar recordsize a ciegas

Síntoma: Workloads de lectura aleatoria muestran alta latencia; los escaneos secuenciales muestran I/O enorme; el prefetch parece “peor de lo habitual”.

Por qué ocurre: Registros sobredimensionados inflan la amplificación de lectura; prefetch trae bloques grandes que contienen poca información útil para lecturas pequeñas.

Corrección: Alinea recordsize con la carga (p. ej., más pequeño para bases de datos, más grande para backups/medios). Vuelve a probar el comportamiento de prefetch después de tener recordsize sensato.

Error 6: Hacer varios cambios a la vez

Síntoma: Tras el “tuning”, el rendimiento cambia pero nadie puede explicar por qué; regresiones posteriores son imposibles de depurar.

Corrección: Cambia una variable, mide, conserva un rollback. El tuning de prefetch es sensible a la mezcla de cargas; necesitas atribución.

Listas de verificación / plan paso a paso

Plan paso a paso: prueba (o descarta) thrash por prefetch

  1. Marca la ventana del incidente. Anota cuándo empezó la lentitud y qué jobs se ejecutaban.
  2. Revisa la salud del pool. Si scrub/resilver está activo, anótalo.
  3. Recopila estadísticas de ARC durante 2–5 minutos. Usa arcstat 1 y captura la tendencia de miss%.
  4. Recopila estadísticas de I/O del pool. Usa zpool iostat -v 1 para ver la carga de lectura.
  5. Encuentra los mayores lectores. Usa iotop (o el equivalente de tu plataforma).
  6. Correlaciona. ¿Las misses del ARC se dispararon cuando empezó el lector secuencial?
  7. Elige la mitigación menos invasiva. Limita/reprograma el lector primero.
  8. Si debes afinar: desactiva temporalmente prefetch (o reduce su alcance) y mide en una ventana corta.
  9. Decide. Si la latencia mejora materialmente y las tareas secuenciales siguen aceptables, considera un cambio persistente con documentación.
  10. Acción post-mortem: Reejecuta la carga en una ventana controlada y valida la solución bajo concurrencia realista.

Lista: higiene segura de afinado en producción

  • Tener un comando de rollback listo antes de aplicar el cambio.
  • Cambiar solo una cosa a la vez.
  • Medir tanto métricas del sistema (ARC/pool) como métricas de la carga (tiempo de job, latencia de consultas).
  • Preferir afinado por rol (nodos de backup vs nodos sensibles a latencia) antes que interruptores en toda la flota.
  • Escribe el “por qué”, no solo el “qué”. El tú del futuro no recordará el contexto de pánico.

Preguntas frecuentes

1) ¿Qué es exactamente el prefetch de ZFS?

Es la lectura anticipada adaptativa de ZFS. Cuando ZFS detecta acceso secuencial, emite lecturas adicionales para bloques futuros para que estén en ARC cuando la aplicación los solicite.

2) ¿Prefetch es lo mismo que readahead de Linux?

No. El readahead de Linux está ligado a la caché de páginas y a la capa de bloque. ZFS usa ARC y su propia canalización de I/O. Puedes tener un readahead “razonable” de Linux y aun así tener prefetch de ZFS causando churn en ARC.

3) ¿Cuándo debo desactivar el prefetch?

Cuando tengas evidencia sólida de que está contaminando ARC y perjudicando cargas sensibles a latencia—típicamente durante escaneos secuenciales grandes de una sola vez en un sistema compartido con poco margen en ARC. Desactívalo primero como experimento controlado.

4) ¿Cuándo es mala idea desactivar el prefetch?

Si tu entorno depende de lecturas secuenciales de alto throughput (backups, streaming de medios, lecturas grandes, streams de zfs send) y no tienes otras maneras de programarlas/limitarlas. Desactivarlo puede convertir lecturas secuenciales suaves en E/S constante en disco.

5) ¿Cómo sé que es thrash por prefetch y no solo “poca RAM”?

La falta de RAM suele ser la condición subyacente, pero el thrash por prefetch es el desencadenante. Verás ARC anclado cerca del máximo, miss% subiendo durante un trabajo secuencial, aumento de lecturas al disco y latencia de cola degradada para cargas no relacionadas. Si la misma huella de memoria se comporta bien sin el escaneo, las lecturas especulativas son parte del mecanismo.

6) ¿L2ARC soluciona el thrash por prefetch?

Normalmente no. L2ARC se alimenta de expulsiones del ARC; si ARC está lleno de bloques prefetechados de una sola vez, L2ARC almacenará también esa basura, con sobrecarga adicional en RAM. L2ARC ayuda cuando hay reutilización real que no cabe en ARC.

7) ¿Puedo afinar prefetch sin desactivarlo?

A menudo sí, dependiendo de la versión de OpenZFS y la plataforma. Puedes limitar cuánto avanza ZFS o cuántos streams rastrea. Los parámetros exactos varían, por eso es importante inspeccionar lo que tu sistema expone en /sys/module/zfs/parameters (Linux) o interfaces loader/sysctl (FreeBSD).

8) ¿Por qué el prefetch a veces reduce el rendimiento incluso en NVMe rápido?

Porque el coste no es solo la latencia del dispositivo. La contaminación de caché expulsa metadata y bloques calientes, creando más misses y más E/S. Incluso si NVMe es rápido, la latencia de cola incrementada y el trabajo adicional de I/O aún pueden perjudicar los tiempos de respuesta de la aplicación.

9) ¿Cuál es la relación entre recordsize y prefetch?

Prefetch avanza en unidades que finalmente se mapean a bloques en disco. Los registros más grandes pueden aumentar la cantidad de datos leídos de forma especulativa. Si tu app lee aleatoriamente 8–16K y tu recordsize es 1M, el prefetch y la amplificación de lectura se vuelven muy caros.

10) Si desactivo prefetch, ¿ARC dejará de cachear lecturas?

No. ARC seguirá cacheando las lecturas a demanda (lo que la aplicación realmente solicitó). Desactivar prefetch reduce principalmente las lecturas especulativas por adelantado.

Conclusión

El prefetch de ZFS no es ni héroe ni villano. Es un ayudante agresivo que asume que el mañana se parece a hoy: las lecturas secuenciales continuarán y cachear por adelantado dará fruto. En cargas de streaming estables, esa suposición es correcta y el rendimiento es excelente. En la realidad corporativa mixta, multi-tenant y cargada de escaneos, esa suposición se rompe—y la factura llega como thrash de ARC y latencia de cola.

La jugada ganadora no es “pulsar el bit mágico”. La jugada ganadora es diagnóstico disciplinado: probar la correlación, identificar el escaneo, proteger la metadata y los datos calientes, y aplicar la corrección más estrecha que restaure la predictibilidad. Cuando tratas el prefetch como una hipótesis en vez de una superstición, ZFS vuelve a ser aburrido. Y el almacenamiento aburrido es el mejor almacenamiento.

← Anterior
Ubuntu 24.04: sudo lento — correcciones de DNS/hostname que eliminan la demora (caso #66)
Siguiente →
Docker en Windows/WSL2 es lento: soluciones que realmente ayudan

Deja un comentario