Recordsize y compresión en ZFS: la combinación que cambia la aritmética de CPU y disco

¿Te fue útil?

Puedes comprar discos más rápidos, lanzar NVMe al problema y aun así ver las gráficas de latencia como un sismógrafo.
Luego cambias dos propiedades de ZFS—recordsize y compression—y de repente el mismo hardware parece haber recibido un ascenso.

O las ajustas mal y tus CPUs empiezan a hacer danza interpretativa mientras los discos duermen la siesta. Aquí es donde dejamos de adivinar y hacemos las cuentas.

Qué hace realmente recordsize (y qué no hace)

recordsize es el tamaño máximo de un bloque de datos que ZFS usará para un archivo en un dataset de sistema de archivos.
No es “el tamaño de bloque” por obligación. Es un techo, una pista y—dependiendo de tu carga—un volante de dirección del rendimiento.

Recordsize es un límite, no un mandato

Para archivos ordinarios, ZFS usa registros de tamaño variable hasta recordsize. Un archivo con escrituras de 4K no se convierte mágicamente en bloques de 128K
solo porque el recordsize del dataset sea 128K. ZFS almacenará bloques pequeños cuando el patrón de escritura lo requiera.
Pero cuando la carga produce escrituras secuenciales grandes, recordsize se convierte en tu realidad en disco.

Por esto recordsize es más visible en:

  • lecturas/escrituras de gran streaming (backups, stores de objetos, multimedia, logs que se añaden en grandes trozos),
  • datasets con archivos grandes y beneficios de read-ahead,
  • cargas donde aparecen penalizaciones por read-modify-write (sobrescrituras aleatorias dentro de bloques grandes).

Recordsize no se aplica a zvols de la misma manera

Si sirves iSCSI/LUNs o discos de VM vía zvols, la perilla es volblocksize, fijada en la creación del zvol.
Ese es el tamaño de bloque fijo expuesto al consumidor. Trátalo como un contrato de API.

El villano oculto: escrituras parciales de bloque

El modo doloroso de “recordsize demasiado grande” no es que ZFS no pueda hacer I/O pequeño; es que las sobrescrituras de trozos pequeños dentro de un registro grande
pueden desencadenar read-modify-write (RMW). ZFS debe leer el bloque viejo, modificar parte de él y escribir un nuevo bloque completo (copy-on-write).
Eso cuesta I/O extra y mayor latencia—especialmente cuando el working set no cabe en ARC y haces escrituras síncronas.

Piensa en recordsize como elegir la unidad de “tamaño de transacción de almacenamiento”. Bloques mayores reducen overhead de metadatos y pueden aumentar el throughput en streaming.
Bloques mayores también aumentan el radio de impacto de una sobrescritura pequeña.

Broma #1: Configurar recordsize=1M para una base de datos con escrituras aleatorias es como llevar un camión de mudanza para entregar una llave de casa. Llegará, pero nadie estará contento.

La compresión cambia la economía

La compresión de ZFS no es solo una función de espacio. Es una función de rendimiento porque intercambia ciclos de CPU por I/O físico reducido.
Ese intercambio puede ser fantástico o terrible dependiendo de cuál sea tu cuello de botella.

El efecto central: bytes lógicos vs bytes físicos

Cada dataset ZFS tiene dos realidades:

  • Lógico: lo que la aplicación cree que escribió/leyó.
  • Físico: lo que realmente llegó al disco tras la compresión (y potencialmente después de efectos de padding, paridad RAIDZ, etc.).

Cuando la compresión es efectiva, reduces lecturas/escrituras físicas. Eso significa:

  • menor consumo de ancho de banda de disco,
  • potencialmente menos IOPS si la misma solicitud lógica se traduce en menos trabajo físico,
  • ARC más efectivo (porque los bloques comprimidos en caché representan más datos lógicos por byte de RAM).

LZ4 suele ser el predeterminado por una razón

En producción, compression=lz4 es la configuración “segura y rápida” para la mayoría de los datos: configuraciones, logs, payloads textuales, muchas bases de datos, imágenes de VM.
Es lo suficientemente rápido como para que CPU rara vez sea el factor limitante a menos que estés a un throughput muy alto o con núcleos poco potentes.

Compresores más agresivos (niveles de zstd, por ejemplo) pueden ganar mucho en espacio y a veces en reducción de I/O, pero el coste en CPU se vuelve real.
Ese coste de CPU aparece en la latencia en momentos inconvenientes: ráfagas de compactación, ventanas de backup y “¿por qué la API está lenta solo durante la replicación?”.

La compresión también interactúa con recordsize

La ratio de compresión es sensible al tamaño de bloque. Registros mayores a menudo comprimen mejor porque el compresor ve más redundancia.
Por eso aumentar recordsize puede mejorar ratios de compresión en datos tipo log o columnales.
Pero otra vez: si tu carga sobrescribe pequeñas partes de bloques grandes, puedes pagar RMW más el coste de compresión en cada reescritura.

La aritmética de la combinación: IOPS, ancho de banda y CPU

Recordsize y compresión no solo “optimizan.” Deciden qué recurso será tu factor limitante:
IOPS de disco, ancho de banda de disco o CPU.

Comienza con los tres regímenes de cuello de botella

  • Limitado por IOPS: la latencia está dominada por el número de operaciones (I/O aleatorio, cargas con muchos metadatos, escrituras síncronas).
  • Limitado por ancho de banda: no puedes pasar más bytes por discos/red (streaming, escaneos, backups).
  • Limitado por CPU: compresión/descompresión, checksums, cifrado o mucha contabilidad de ZFS consumen los núcleos.

Tu objetivo de afinamiento no es “máximo rendimiento.” Es “mover el cuello de botella al recurso más barato.”
La CPU suele ser más barata que las IOPS en flash, y enormemente más barata que las IOPS en discos giratorios. Pero la CPU no es gratis cuando los presupuestos de latencia son estrictos.

Recordsize cambia la aritmética de IOPS

Supongamos que tienes una carga leyendo 1 GiB secuencialmente.
Si tu recordsize es 128K, eso son alrededor de 8192 bloques. Si es 1M, son unos 1024 bloques.
Menos bloques significa menos búsquedas de metadatos, menos validaciones de checksum, menos operaciones de I/O y mejor comportamiento de prefetch.
Para lecturas en streaming, bloques más grandes suelen ganar.

Ahora cambia a sobrescrituras aleatorias de páginas de 8K (hola bases de datos).
Si esas páginas viven dentro de registros de 128K, una actualización aleatoria de página puede traducirse en:

  • leer el 128K antiguo (si no está en ARC),
  • modificar 8K dentro de él,
  • escribir un nuevo 128K en otro lugar (copy-on-write),
  • actualizar metadatos.

Eso es más que “una escritura de 8K”. Es un patrón de I/O amplificado, además de fragmentación con el tiempo.
Con recordsize=16K (o 8K dependiendo del tamaño de página de la BD), reduces la amplificación RMW.
Puedes aumentar overhead de metadatos, pero las bases de datos ya viven en ese mundo.

La compresión cambia la aritmética de ancho de banda (y a veces de IOPS)

Si tu ratio de compresión es 2:1, una “lectura de 100 MB” se convierte en “50 MB lectura física + descompresión”.
Si tus discos estaban saturados, acabas de liberar la mitad del ancho de banda. La latencia baja, el throughput sube, todos parecen inteligentes.
Si tus discos no estaban saturados y tus CPUs ya estaban ocupadas, te acabas de comprar un nuevo problema.

Aritmética de CPU: la descompresión está en el camino de lectura

Las escrituras pagan el coste de compresión. Las lecturas pagan el coste de descompresión. En muchos sistemas, las lecturas dominan la latencia en cola.
Eso significa que el comportamiento del descompresor importa más de lo que crees.
LZ4 suele ser rápido; los compresores de mayor nivel pueden no serlo.

ARC y compresión: “más caché” sin comprar RAM

ZFS guarda bloques comprimidos en ARC (los detalles de implementación varían por versión y flags de características, pero el efecto práctico se mantiene:
los datos comprimidos hacen la caché más efectiva).
Mejor compresión puede significar más datos lógicos en caché por GB de RAM.
Esta es una razón por la que compression=lz4 es un “sí” por defecto para datasets de propósito general.

Cuando la combinación es mágica

El escenario de mejor caso:

  • recordsize relativamente grande para el patrón de acceso de la carga,
  • compresión barata (lz4) y efectiva (mucho redundancia),
  • los discos son el recurso caro y las CPUs tienen holgura.

El resultado son menos bytes físicos, menos paradas de disco y mayores tasas de aciertos en caché.

Cuando la combinación es una trampa

El escenario trampa:

  • recordsize mayor que la granularidad de sobrescritura,
  • la carga hace actualizaciones aleatorias,
  • la compresión añade coste de CPU a cada reescritura de un registro grande,
  • escrituras síncronas + dispositivo SLOG pequeño (o sin SLOG) magnifica la latencia.

El síntoma suele ser “el disco no está ocupado pero la latencia es terrible.” Eso es contención en CPU o en la ruta síncrona, no ancho de banda bruto.

Guía por cargas de trabajo: qué ajustar según cada caso

Existen valores por defecto porque son seguros, no porque sean óptimos. Tu trabajo es ser opinable de forma segura.

Compartición de archivos general, directorios personales, configuraciones

  • recordsize: deja el valor por defecto (a menudo 128K) a menos que tengas una razón.
  • compression: lz4.
  • Por qué: cargas mixtas se benefician de un recordsize medio; la compresión reduce I/O físico para archivos con mucho texto.

Imágenes de VM en datasets de sistema de archivos (qcow2/raw files)

  • recordsize: comúnmente 16K o 32K; a veces 64K si predominan lecturas secuenciales.
  • compression: lz4 (a menudo ayuda más de lo que la gente espera).
  • Por qué: I/O de VM tiende a ser algo aleatorio y en bloques más pequeños; reduce la amplificación RMW.

zvols para discos de VM / iSCSI

  • volblocksize: coincide con la expectativa del guest/filesystem (a menudo 8K o 16K; a veces 4K).
  • compression: lz4 salvo que la CPU esté ajustada.
  • Por qué: volblocksize es fijo; equivocarse es un impuesto permanente a menos que migres.

PostgreSQL / MySQL / bases de datos en datasets de sistema de archivos

  • recordsize: alinea con el tamaño de página de la BD o un múltiplo que no cause churn (a menudo 8K o 16K).
  • compression: lz4 suele estar bien; prueba zstd solo si tienes CPU y buscas espacio.
  • Por qué: las bases de datos hacen sobrescrituras aleatorias pequeñas; registros grandes conducen a RMW y fragmentación.

Backups, archivos, medios, blobs tipo objeto

  • recordsize: 512K o 1M puede tener sentido si las lecturas/escrituras son en streaming.
  • compression: zstd (nivel moderado) puede valer la pena, o lz4 si quieres CPU predecible.
  • Por qué: I/O secuencial recompensa bloques grandes; la ratio de compresión mejora con ventanas mayores.

Logs (append-heavy)

  • recordsize: el valor por defecto suele estar bien; si los logs son grandes y mayormente se leen secuencialmente después, considera 256K.
  • compression: lz4 casi siempre gana.
  • Por qué: los logs comprimen extremadamente bien; reducir escrituras físicas también reduce desgaste en SSDs.

Cargas de archivos pequeños (maildirs, árboles de código, cachés de paquetes)

  • recordsize: no es la perilla principal; los archivos pequeños ya usan bloques pequeños.
  • compression: lz4 ayuda y puede acelerar lecturas al reducir I/O de disco.
  • Considera: un vdev especial para metadatos/bloques pequeños si te tomas en serio la latencia.

Datos interesantes y contexto histórico

  • ZFS se construyó para integridad de datos de extremo a extremo: checksums en cada bloque son nucleares, no un añadido.
  • Los primeros valores por defecto de ZFS apuntaban a discos grandes y cargas mixtas: recordsize 128K se volvió un término medio pragmático para streaming y uso general.
  • La compresión en ZFS ha sido “transparente” por mucho tiempo: las aplicaciones no necesitan saber; el sistema de archivos decide la representación en disco.
  • LZ4 se volvió la opción por defecto porque es barato: fue diseñado para velocidad, así que a menudo mejora rendimiento al reducir I/O sin disparar CPU.
  • Copy-on-write cambia el coste de las sobrescrituras: ZFS nunca sobrescribe en el lugar; por eso las cargas con muchas sobrescrituras son sensibles al dimensionamiento de bloques.
  • La amplificación de escritura en RAIDZ es real: las escrituras aleatorias pequeñas pueden convertirse en grandes ciclos read-modify-write a nivel de stripe de paridad, lo que complica las decisiones de recordsize.
  • ARC popularizó “RAM como almacenamiento”: ZFS cachea agresivamente, y la compresión efectivamente aumenta la capacidad de caché para datos compresibles.
  • zvols se crearon para consumidores de bloque: pero su volblocksize fijo hace que los errores sean más difíciles de deshacer que recordsize en sistemas de archivos.

Tres microhistorias corporativas desde el campo

Incidente: la suposición equivocada (“recordsize no importa para bases de datos, ¿verdad?”)

Una empresa mediana ejecutaba una plataforma de analítica de clientes en ZFS. La base de datos principal vivía en un dataset clonado de un objetivo de backup.
Nadie se dio cuenta, porque montaba bien, el rendimiento parecía “aceptable” y las gráficas estaban tranquilas—hasta fin de mes.

Fin de mes implicaba actualizaciones pesadas, churn de índices y trabajos de mantenimiento. La latencia subió y luego se disparó. Los hilos de la aplicación se acumularon.
El equipo de almacenamiento miró la utilización de disco: no estaba saturado. Las gráficas de CPU en los hosts de BD: tampoco saturadas. Así que culparon a la red.
Afinaron buffers TCP como si fuera 2009.

El problema real fue aburrido: el dataset tenía recordsize=1M, heredado del perfil de backup.
La base de datos escribía páginas de 8K. Bajo churn, ZFS tuvo que hacer read-modify-write de registros grandes y churn de metadatos.
La pool no estaba “ocupada” en términos de ancho de banda; estaba ocupada haciendo el tipo de trabajo equivocado.

Lo arreglaron moviendo la base de datos a un nuevo dataset con recordsize=16K y manteniendo compression=lz4.
La migración fue la parte dolorosa. La recuperación de rendimiento fue inmediata y las gráficas dejaron de gritar.

Lección: “Se montó, por lo tanto está bien” es cómo terminas depurando física a las 2 a.m.

Optimización que salió mal: la fase de entusiasmo por zstd

Otra organización tenía un problema de coste de almacenamiento: pools SSD rápidos se llenaban con imágenes de VM y artefactos de build.
Alguien propuso compresión más fuerte. Activaron compression=zstd a un nivel agresivo en un dataset muy activo.
Los ahorros de espacio fueron grandes. El ticket se cerró con un suspiro satisfecho.

Dos semanas después, su sistema CI empezó a incumplir SLAs de build. No consistentemente—solo lo suficiente para ser enloquecedor.
El cluster no estaba sin CPU en general, pero un subconjunto de nodos mostró iowait elevado y más CPU de sistema en horas pico.
El array de almacenamiento no estaba saturado. La red estaba bien. Así que empezó la gira de culpas: actualizaciones de kernel, afinamiento del scheduler, “tal vez sea DNS”.

El culpable real: la CPU dedicada a comprimir y descomprimir artefactos calientes durante cargas bursty.
La compresión fuerte mejoró la capacidad pero aumentó la latencia en cola y amplificó la contención en los nodos de cómputo más ocupados.
El sistema no falló; simplemente se volvió molesto e impredecible—el peor tipo de regresión en producción.

Revirtieron a lz4 en el dataset caliente y dejaron zstd solo para archivos de artefactos fríos y de solo-escritura.
El resultado correcto no fue “nunca usar zstd.” Fue “úsalo donde el patrón de acceso encaje con el presupuesto de CPU.”

Práctica aburrida pero correcta que salvó el día: datasets por carga

Una firma de servicios financieros tenía una regla clara: cada carga obtiene su propio dataset, aunque parezca papeleo.
Bases de datos, logs, imágenes de VM, backups—datasets separados, propiedades explícitas y un README corto en el punto de montaje.
Los ingenieros nuevos se quejaban. Los mayores sonreían con educación y seguían haciéndolo.

Un día apareció una regresión de rendimiento después de una actualización de plataforma. Los arranques masivos de VM eran más lentos y un subconjunto de invitados tenía latencia de escritura alta.
Porque la organización tenía datasets separados, pudieron comparar propiedades rápidamente y ver qué cargas se veían afectadas.
El dataset de VM tenía un recordsize afinado para ese entorno y la compresión era consistente.

El problema resultó estar en otra parte (una ruta de escritura síncrona combinada con un dispositivo SLOG que se portaba mal),
pero aislar datasets evitó un intento desordenado de “afinamiento global” que habría roto sus bases de datos.
Arreglaron el problema del SLOG y todo lo demás permaneció estable porque nunca se tocó.

Lección: la corrección suele ser simplemente separación disciplinada. No es glamoroso. Es cómo evitas afinar una carga a costa de sabotear tres más.

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

Estos son movimientos operativos reales. Cada tarea incluye un comando, una salida de ejemplo, lo que significa y qué decisión tomar después.
Ajusta nombres de pool/dataset para que coincidan con tu entorno.

Task 1: Listar propiedades del dataset que importan (recordsize, compression)

cr0x@server:~$ zfs get -o name,property,value -s local,inherited recordsize,compression rpool/data
NAME        PROPERTY     VALUE
rpool/data  recordsize   128K
rpool/data  compression  lz4

Significado: Ves las configuraciones efectivas. Si están heredadas, rastrea de dónde vienen.

Decisión: Si este dataset aloja una BD o imágenes de VM, 128K podría estar mal. No cambies todavía—mide primero.

Task 2: Comprobar si un dataset está realmente comprimiendo datos

cr0x@server:~$ zfs get -o name,property,value compressratio,logicalused,used rpool/data
NAME        PROPERTY       VALUE
rpool/data  compressratio  1.85x
rpool/data  logicalused    1.62T
rpool/data  used           906G

Significado: La compresión está funcionando. Logical es lo que las apps escribieron; used es el espacio físico consumido.

Decisión: Si compressratio está cerca de 1.00x en datos calientes, la compresión podría ser CPU desperdiciada. Considera dejar lz4 de todos modos a menos que la CPU esté ajustada.

Task 3: Encontrar propiedades heredadas rápidamente (¿quién lo configuró?)

cr0x@server:~$ zfs get -o name,property,value,source recordsize,compression -r rpool
NAME                  PROPERTY     VALUE  SOURCE
rpool                 recordsize   128K   default
rpool                 compression  off    default
rpool/data            recordsize   128K   inherited from rpool
rpool/data            compression  lz4    local
rpool/data/db         recordsize   1M     local
rpool/data/db         compression  lz4    inherited from rpool/data

Significado: rpool/data/db tiene un recordsize local de 1M. Eso es una señal de alarma para muchas bases de datos.

Decisión: Confirma el tipo de carga. Si es una BD con páginas de 8K, planifica migración a un dataset con tamaño correcto.

Task 4: Ver I/O en tiempo real por dataset para encontrar al vecino ruidoso

cr0x@server:~$ zpool iostat -v rpool 2
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
rpool                       2.10T  1.40T    180    950  12.3M  88.1M
  mirror                    2.10T  1.40T    180    950  12.3M  88.1M
    nvme0n1                 -      -        90    480  6.2M   44.0M
    nvme1n1                 -      -        90    470  6.1M   44.1M

Significado: Muchas ops de escritura frente a ops de lectura modestas sugiere carga orientada a escritura. No es suficiente por sí solo—correlaciona con latencia.

Decisión: Si el ancho de banda es bajo pero las ops son altas y la latencia es mala, sospecha escrituras aleatorias pequeñas y/o problemas en la ruta síncrona.

Task 5: Comprobar salud del pool y errores antes de afinar rendimiento

cr0x@server:~$ zpool status -v rpool
  pool: rpool
 state: ONLINE
status: Some supported features are not enabled on the pool.
action: Enable all features using 'zpool upgrade'. Once this is done,
        the pool may no longer be accessible by software that does not support the features.
  scan: scrub repaired 0B in 00:12:41 with 0 errors on Tue Dec 24 03:12:02 2025
config:

        NAME        STATE     READ WRITE CKSUM
        rpool       ONLINE       0     0     0
          mirror    ONLINE       0     0     0
            nvme0n1 ONLINE       0     0     0
            nvme1n1 ONLINE       0     0     0

errors: No known data errors

Significado: Sin errores, scrub limpio. Bien—los problemas de rendimiento probablemente sean por configuración o carga.

Decisión: Procede con diagnóstico de carga. No “afines” un pool que está fallando silenciosamente en el medio.

Task 6: Medir el coste de CPU de la compresión vía tiempo de sistema bajo carga

cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.5.0 (server)  12/26/2025  _x86_64_  (16 CPU)

12:40:01 AM  CPU  %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
12:40:02 AM  CPU  all  18.1  0.0   9.7   2.4    0.0  0.3    0.0    0.0    0.0   69.5
12:40:03 AM  all  20.2  0.0  13.5   1.9    0.0  0.2    0.0    0.0    0.0   64.2
12:40:04 AM  all  19.6  0.0  14.1   1.8    0.0  0.2    0.0    0.0    0.0   64.3

Significado: %sys elevado sugiere trabajo del kernel (ZFS incluido). iowait bajo sugiere que los discos no son el cuello de botella.

Decisión: Si el rendimiento es pobre con iowait bajo, investiga rutas limitadas por CPU (nivel de compresión, checksumming, escrituras síncronas, contención).

Task 7: Observar efectividad de ARC y presión de memoria

cr0x@server:~$ arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:41:10   820   110     13    28    3    72    9    10    1   28.5G  31.8G
12:41:11   790    95     12    22    3    63    8    10    1   28.6G  31.8G
12:41:12   815   120     15    35    4    74    9    11    1   28.6G  31.8G

Significado: La tasa de misses no es terrible. El tamaño de ARC está cerca del objetivo. La compresión puede hacer que ARC “guarde más datos lógicos”, pero los misses aún duelen.

Decisión: Si miss% es alto en una carga de lectura, la compresión puede ayudar (menos lecturas físicas), pero también podrías necesitar más RAM o mejor localidad.

Task 8: Comprobar por dataset escritos lógicos vs físicos (¿la compresión reduce la carga de escritura?)

cr0x@server:~$ zfs get -o name,property,value -r logicalused,used,compressratio rpool/data/vm
NAME           PROPERTY       VALUE
rpool/data/vm  logicalused    800G
rpool/data/vm  used           610G
rpool/data/vm  compressratio  1.31x

Significado: Compresión moderada. Las imágenes de VM a menudo comprimen un poco, a veces mucho dependiendo del OS y bloques cero.

Decisión: Mantén lz4. Si la CPU está alta y compressratio está cerca de 1.00x, considera dejarla activada solo si no persigues latencia en cola.

Task 9: Confirmar los tamaños reales de bloque escritos (no solo recordsize)

cr0x@server:~$ zdb -bbbbb rpool/data/db | head -n 12
Dataset rpool/data/db [ZPL], ID 236, cr_txg 41292, 1.12G, 2948 objects
Indirect blocks:
               0 L0  16K   1.23G  11%  1.00x   79.3M

Significado: Esto muestra la distribución real de bloques. Aquí ves predominio de bloques L0 de 16K, a pesar de que el recordsize pudiera ser mayor.

Decisión: Si esperabas bloques de 128K para una carga en streaming pero ves muchos de 8K/16K, la carga no es streaming (o está fragmentada). Afina acorde.

Task 10: Identificar si las escrituras síncronas te están matando

cr0x@server:~$ zfs get -o name,property,value sync,logbias rpool/data/db
NAME          PROPERTY  VALUE
rpool/data/db sync      standard
rpool/data/db logbias   latency

Significado: Sync está en standard (seguro). logbias latency prefiere uso de SLOG cuando está presente.

Decisión: Si tienes muchas fsync y no buen SLOG, espera dolor de rendimiento. No “arregles” poniendo sync=disabled a menos que disfrutes explicar pérdida de datos.

Task 11: Comprobar si existe un vdev especial (aceleración de metadatos/bloques pequeños)

cr0x@server:~$ zpool status rpool | sed -n '1,40p'
  pool: rpool
 state: ONLINE
config:

        NAME           STATE     READ WRITE CKSUM
        rpool          ONLINE       0     0     0
          mirror       ONLINE       0     0     0
            sda        ONLINE       0     0     0
            sdb        ONLINE       0     0     0
        special
          mirror       ONLINE       0     0     0
            nvme0n1    ONLINE       0     0     0
            nvme1n1    ONLINE       0     0     0

Significado: Hay un mirror special. Metadatos y opcionalmente bloques pequeños pueden aterrizar allí.

Decisión: Si latencia de archivos pequeños y metadatos es tu problema, special vdev puede ser importante. Pero también es un compromiso de fiabilidad—si lo pierdes puedes perder la pool.

Task 12: Verificar setting special_small_blocks (¿los datos pequeños van realmente al special?)

cr0x@server:~$ zfs get -o name,property,value special_small_blocks rpool/data
NAME        PROPERTY             VALUE
rpool/data  special_small_blocks 0

Significado: Solo los metadatos van al special, no los bloques pequeños de datos.

Decisión: Si quieres bloques pequeños en special, fija special_small_blocks=16K o similar—después de pensar bien sobre capacidad y dominio de fallo.

Task 13: Probar un cambio de recordsize de forma segura en un dataset nuevo

cr0x@server:~$ zfs create -o recordsize=16K -o compression=lz4 rpool/data/db16k
cr0x@server:~$ zfs get -o name,property,value recordsize,compression rpool/data/db16k
NAME            PROPERTY     VALUE
rpool/data/db16k recordsize  16K
rpool/data/db16k compression lz4

Significado: Creaste un lugar para migrar/probar sin mutar el dataset existente en sitio.

Decisión: Migra una porción representativa de datos y haz benchmark. Evita las heroicidades de “cambiar en producción”.

Task 14: Mover datos con send/receive para preservar snapshots y reducir downtime

cr0x@server:~$ zfs snapshot rpool/data/db@pre-migrate
cr0x@server:~$ zfs send -R rpool/data/db@pre-migrate | zfs receive -u rpool/data/db16k
cr0x@server:~$ zfs list -t snapshot -o name,used -r rpool/data/db16k | head
NAME                         USED
rpool/data/db16k@pre-migrate 0B

Significado: El dataset y el snapshot existen en el destino. -u lo mantiene desmontado hasta que estés listo.

Decisión: Haz el corte intercambiando puntos de montaje o actualizando la configuración del servicio durante una ventana de mantenimiento.

Task 15: Validar que realmente reduces I/O físico después de activar compresión

cr0x@server:~$ zfs get -o name,property,value written,logicalused rpool/data/logs
NAME            PROPERTY    VALUE
rpool/data/logs written     145G
rpool/data/logs logicalused 312G

Significado: Logical used es mucho mayor que lo escrito físicamente, lo que sugiere que la compresión está reduciendo tráfico de escritura.

Decisión: Para logs append-heavy, mantén la compresión activada. Reduce desgaste en disco y a menudo mejora la velocidad de lectura en investigaciones.

Guion rápido de diagnóstico

Este es el flujo de trabajo para cuando “alguien está gritando en Slack”. El objetivo es identificar si estás limitado por IOPS, ancho de banda, CPU o por la ruta síncrona—rápido.

Primero: confirma que no es un pool fallo o una scrub/resilver en curso

  • Ejecuta zpool status. Si ves errores, vdevs degradados o un resilver en curso, para de afinar y empieza a arreglar hardware o terminar la recuperación.
  • Comprueba si hay un scrub en ejecución. Los scrubs pueden ser educados o agresivos según tunables y carga.

Segundo: decide si el cuello de botella es disco o CPU

  • Señales de limitación por disco: iowait alto, utilización del dispositivo alta, ancho de banda cercano al límite del dispositivo, latencia que crece con el throughput.
  • Señales de limitación por CPU: iowait bajo pero CPU de sistema alto, el throughput se estanca temprano, picos de latencia sin saturar discos.

Tercero: prueba si las escrituras síncronas son el verdadero villano

  • Revisa la propiedad sync del dataset y si la carga usa fsync/O_DSYNC.
  • Si tienes un SLOG, valida que esté sano y rápido. Si no lo tienes, acepta que cargas pesadas en fsync estarán limitadas por la latencia del vdev principal.

Cuarto: comprueba recordsize/volblocksize frente al patrón de I/O

  • Para BD/overwrite aleatorio, recordsize demasiado grande puede causar amplificación RMW.
  • Para cargas en streaming, recordsize demasiado pequeño desperdicia IOPS y CPU en metadatos/checksums.

Quinto: verifica que la compresión esté ayudando, no solo “activada”

  • Mira compressratio y uso lógico vs físico.
  • Si la ratio es pobre y la CPU está alta, considera lz4 en lugar de algoritmos más pesados, o apaga la compresión para datos ya comprimidos.

Errores comunes: síntoma → causa raíz → arreglo

1) “Los discos están ociosos pero la latencia es alta”

  • Síntoma: ancho de banda bajo, iowait bajo, pero picos de latencia en la aplicación.
  • Causa raíz: ruta limitada por CPU (compresión demasiado agresiva, coste de checksum/cifrado) o contención en escrituras síncronas.
  • Arreglo: degradar compresión a lz4, asegurar holgura de CPU, validar SLOG para cargas síncronas y evitar registros sobredimensionados para patrones de sobrescritura.

2) “Las escrituras de la base de datos se volvieron más lentas tras aumentar recordsize para throughput”

  • Síntoma: mayor latencia de escritura, picos impredecibles durante mantenimiento/vacuum/compactación.
  • Causa raíz: amplificación RMW por sobrescribir páginas pequeñas dentro de registros grandes; la fragmentación empeora con el tiempo.
  • Arreglo: migra a un dataset con recordsize alineado al tamaño de página de la BD (a menudo 8K/16K). Mantén compression=lz4 salvo que la CPU sea un problema.

3) “Compresión activada, pero la pool sigue llenándose y el rendimiento no mejoró”

  • Síntoma: compressratio ~1.00x, sin reducción significativa de escrituras físicas.
  • Causa raíz: los datos ya están comprimidos/encriptados (media, zip, muchas imágenes de VM con sistemas de archivos cifrados).
  • Arreglo: deja lz4 si la CPU es barata y quieres ganancias ocasionales; de lo contrario apaga compresión en ese dataset y deja de pagar la tasa de CPU por nada.

4) “Las lecturas secuenciales son más lentas de lo esperado en discos rápidos”

  • Síntoma: throughput por debajo de la capacidad hardware durante escaneos/backups.
  • Causa raíz: recordsize demasiado pequeño, causando IOPS excesivos y overhead; o datos fragmentados en bloques pequeños por patrón histórico de escritura.
  • Arreglo: usa un dataset de registros grandes para datos de streaming (256K–1M). Para datos existentes, considera reescribir (send/recv a un nuevo dataset) para reblockear.

5) “Activar compresión fuerte ahorró espacio pero rompió SLAs”

  • Síntoma: ganancias de espacio, pero latencia en cola y regresiones bajo carga pico.
  • Causa raíz: contención de CPU por compresión/descompresión pesada en la ruta caliente.
  • Arreglo: usa lz4 para datos calientes; reserva zstd para datasets fríos y menos sensibles a latencia; mide con concurrencia de producción.

6) “Cambiamos recordsize y no pasó nada”

  • Síntoma: ninguna diferencia observable después de fijar recordsize.
  • Causa raíz: recordsize afecta escrituras nuevas. Los bloques existentes mantienen su tamaño hasta que se reescriben.
  • Arreglo: reescribe datos vía replicación/migración (send/recv) o reescritura a nivel de aplicación; valida con zdb la distribución de bloques.

Broma #2: Si cambias recordsize en un dataset y esperas que los bloques antiguos se reconfiguren solos, felicidades—has inventado yoga de almacenamiento.

Listas de verificación / plan paso a paso

Paso a paso: elegir recordsize + compresión de forma segura

  1. Clasifica la carga: overwrite aleatorio (BD), lectura aleatoria (VM), secuencial (backup/media), mixto (home dirs).
  2. Encuentra la unidad de I/O: tamaño de página BD, tamaño de bloque VM, tamaño típico de objeto, tamaño de chunk de log.
  3. Elige un recordsize inicial:
    • BD: 8K–16K (alinear páginas).
    • Archivos VM: 16K–64K.
    • Streaming: 256K–1M.
    • Mixto: 128K.
  4. Activa compression lz4 salvo que tengas una razón concreta relacionada con CPU/latencia.
  5. Crea un dataset nuevo con esas propiedades; no mutes el antiguo si puedes evitarlo.
  6. Migra una muestra representativa y haz benchmark con concurrencia parecida a producción.
  7. Valida resultados:
    • compressratio mejoró?
    • ¿Mejoraron los percentiles de latencia?
    • ¿La CPU mantiene holgura?
  8. Despliega gradualmente: migra servicio por servicio, no “toda la pool de una vez”.

Checklist: antes de tocar perillas en producción

  • Pool sano (zpool status).
  • Scrub reciente completado sin errores.
  • Sabes si la carga es heavy en sync.
  • Puedes revertir (snapshots + plan de migración probado).
  • Tienes métricas base (percentiles de latencia, CPU, IOPS, ancho de banda).
  • No estás mezclando cargas no relacionadas en un solo dataset con propiedades de “talla única que no sirve a nadie”.

Checklist: política de compresión por tipo de dato

  • Textos/logs/configs: lz4 activado.
  • Bases de datos: lz4 activado (normalmente), recordsize alineado.
  • Medios (ya comprimidos): opcional; a menudo desactivado a menos que hayas medido ganancias.
  • Datos cifrados en reposo dentro de archivos: la compresión no ayudará; no esperes milagros.
  • Archivos fríos: considera zstd si el presupuesto de CPU está separado del presupuesto de latencia.

Una idea de fiabilidad (parafraseada) que vale la pena conservar

Idea parafraseada: planifica para el fallo, porque todo falla eventualmente—diseña para que los fallos sean sobrevivibles. — Werner Vogels

Preguntas frecuentes

1) ¿Debo activar siempre compression=lz4?

Para la mayoría de los datasets, sí. A menudo reduce I/O físico y mejora la cache efectiva sin coste notable de CPU.
Excepciones: datos ya comprimidos/cifrados en sistemas con CPU escasa y presupuestos de latencia estrictos.

2) Si cambio recordsize, ¿reescribirá los bloques existentes?

No. Recordsize se aplica a escrituras nuevas. Los bloques existentes mantienen su tamaño hasta que se reescriben.
Para “reblockear” normalmente migras datos a un dataset nuevo (send/recv) o reescribes archivos en la capa de aplicación.

3) ¿Qué recordsize debo usar para PostgreSQL?

Puntos de partida comunes son 8K o 16K (las páginas de PostgreSQL suelen ser 8K). 16K puede estar bien si ves algo de comportamiento secuencial.
La respuesta correcta es: alinea con el comportamiento de las páginas y haz benchmark de tu carga, especialmente en tablas con muchas actualizaciones.

4) ¿Y MySQL/InnoDB?

InnoDB suele usar páginas de 16K por defecto. Un recordsize de 16K es un punto de partida sensato para datasets con muchas sobrescrituras.
Si haces escaneos secuenciales grandes y mayormente append, podrías tolerar más grande. Mide antes de ser creativo.

5) ¿Por qué recordsize grande perjudica escrituras aleatorias si ZFS puede almacenar bloques pequeños?

El problema no son las escrituras pequeñas iniciales—son las sobrescrituras parciales y el comportamiento copy-on-write que crean ciclos read-modify-write y fragmentación.
Los registros grandes aumentan la cantidad de datos reescritos por un cambio pequeño.

6) ¿Vale la pena zstd?

A veces. Puede ofrecer mejor compresión que lz4, especialmente en datos fríos o de solo-escritura.
En datasets calientes y sensibles a latencia, puede empeorar la situación por la contención de CPU y la latencia en cola.

7) ¿La compresión ayuda a IOPS o solo al ancho de banda?

Principalmente reduce ancho de banda (bytes). Pero reducir bytes puede indirectamente reducir presión de IOPS si las operaciones completan más rápido,
y mejora la densidad de caché, lo que puede reducir lecturas físicas (y por tanto IOPS) cuando los aciertos de ARC mejoran.

8) Recordsize vs volblocksize: ¿qué ajusto para almacenamiento de VM?

Si almacenas discos de VM como archivos en un dataset, ajusta recordsize. Si usas zvols, ajusta volblocksize al crear.
No los confundas; volblocksize es más difícil de cambiar después.

9) ¿Puedo fijar distintos valores de recordsize dentro del mismo dataset?

No por directorio usando propiedades ZFS estándar. Es por dataset. El patrón práctico es: crea múltiples datasets con propiedades diferentes
y móntalos donde la aplicación espere distinto comportamiento de I/O.

10) ¿Cómo sé si estoy limitado por CPU por la compresión?

Típicamente verás iowait bajo, CPU de sistema elevado y throughput que se estanca antes de que los discos se saturen.
Confirma comparando rendimiento con lz4 vs compresión más pesada en un dataset de prueba, usando concurrencia parecida a producción.

Conclusión: siguientes pasos que puedes hacer hoy

  1. Inventaría tus datasets: lista recordsize, compression y fuentes de herencia. Encuentra el caso de “perfil de backup ejecutando producción” accidental.
  2. Elige una carga que sea sensible a latencia o costosa (base de datos, almacenamiento de VM, caché de build). Dale un dataset dedicado.
  3. Fija valores sensatos:
    • Datos calientes generales: compression=lz4, recordsize=128K.
    • Bases de datos: compression=lz4, recordsize=8K o 16K.
    • Backups en streaming: recordsize=512K o 1M, compresión según presupuesto de CPU.
  4. Migra, no mutes: crea un dataset nuevo con las propiedades correctas y mueve datos usando send/recv. Así evitas regresiones sorpresa.
  5. Vuelve a medir: lógico vs físico, percentiles de latencia, holgura de CPU y la pregunta “¿los discos están realmente ocupados?”.

Recordsize y compresión no son “trucos de afinamiento”. Son decisiones de arquitectura expresadas como dos propiedades.
Tómalas deliberadamente, por carga, y tu presupuesto de hardware llegará más lejos—sin convertir tus CPUs en pasantes no remunerados.

← Anterior
MySQL vs SQLite: hasta dónde puede llegar SQLite antes de arruinar tu sitio
Siguiente →
ZFS L2ARC: la caché SSD que ayuda… hasta que perjudica

Deja un comentario