ZFS recordsize: la única configuración que decide el 80% del rendimiento de archivos

¿Te fue útil?

ZFS tiene una reputación: “configúralo y olvídalo.” En gran parte es merecida—hasta que deja de serlo. Una propiedad de dataset, recordsize, decide silenciosamente si tu almacenamiento se comporta como una pista bien afinada o como una cinta transportadora de equipaje llena de maletas enfadadas. No solucionará todos los problemas, pero amplificará las buenas decisiones y castigará las malas.

Si gestionas sistemas en producción—flotas de máquinas virtuales, bases de datos, directorios NFS, destinos de backup—recordsize es la perilla que tocas cuando el rendimiento “misteriosamente va bien en dev” y luego se vuelve “misteriosamente caro en prod.” Este artículo es una guía de campo: qué controla realmente recordsize, cómo interactúa con la compresión y la caché, y cómo elegir valores que no creen amplificación de escritura oculta ni picos de latencia.

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

recordsize es una propiedad de dataset de ZFS que controla el tamaño máximo de bloque de datos de archivo que ZFS intentará usar para archivos regulares en ese dataset. Piénsalo como “qué tan grande es el trozo que ZFS prefiere cuando puede.” No es una promesa de que cada bloque tendrá ese tamaño.

Matices importantes que importan en producción:

  • Es un máximo, no un mínimo. ZFS usará bloques más pequeños cuando tenga que hacerlo (archivos pequeños, bloques finales, asignaciones fragmentadas o cuando la compresión hace que los bloques sean más pequeños).
  • Se aplica a bloques de datos de archivos. No es lo mismo que volblocksize (para zvols), y no establece directamente tamaños de bloque de metadata.
  • Afecta cuánto dato debe leerse/re-escribirse cuando cambia parte de un archivo. Este es el punto de apoyo del rendimiento: las escrituras aleatorias pequeñas dentro de un registro grande pueden desencadenar un patrón de lectura-modificación-escritura a nivel de bloque.
  • No reescribe retroactivamente los bloques existentes. Cambiar recordsize afecta las nuevas escrituras en adelante. Los bloques antiguos se mantienen como están hasta que se reescriben (por churn normal de la aplicación o por operaciones de reescritura/copia que inicies).

Broma #1 (corta, relevante): Ajustar mal recordsize es como comprar un camión para entregar un solo sobre—técnicamente funciona, pero tu factura de combustible empezará a tener sentimientos.

Por qué esta única configuración domina el rendimiento de archivos

Para muchas cargas, “rendimiento de archivos” se reduce a algunos comportamientos medibles:

  • Latencia para lecturas/escrituras aleatorias pequeñas (bases de datos, imágenes VM, buzones de correo)
  • Rendimiento (throughput) para lecturas/escrituras secuenciales grandes (medios, backups, blobs estilo objeto)
  • Amplificación de escritura cuando actualizaciones pequeñas disparan reescrituras de bloques grandes
  • Eficiencia de caché en ARC/L2ARC: ¿estás cacheando en la granularidad correcta?

recordsize toca las cuatro. Registros mayores implican menos punteros, menos operaciones de I/O para escaneos secuenciales y, a menudo, mejores ratios de compresión. Registros más pequeños implican menos daño colateral cuando cambia una pequeña parte de un archivo y pueden reducir el comportamiento de “leer más de lo pedido” cuando una aplicación hace lecturas aleatorias.

En términos operativos: recordsize es una de las pocas configuraciones de ZFS que (a) es por dataset, (b) es fácil de cambiar de forma segura y (c) produce diferencias inmediatas y medibles. Esa combinación es rara, y por eso termina decidiendo “el 80% del rendimiento de archivos” en la práctica—especialmente cuando el cuello de botella es un desajuste en el patrón de I/O en lugar del ancho de banda bruto del disco.

Hechos & contexto histórico que explican los valores por defecto actuales

Algo de contexto concreto ayuda a explicar por qué existen los valores por defecto y la “sabiduría popular”:

  1. ZFS heredó un mundo donde las páginas de 4K y los sectores de disco de 4K eran comunes. Muchos caminos de I/O del SO y de bases de datos operan naturalmente en unidades de 4K u 8K, lo que influye en tamaños de bloque “razonables” para I/O aleatorio.
  2. El recordsize clásico por defecto en ZFS era 128K. Ese valor no es mágico; es un compromiso orientado a sistemas de archivos de propósito general con mucho I/O secuencial mezclado.
  3. Los primeros discos commodity frecuentemente mentían sobre sectores. La era 512e creó problemas dolorosos de alineación; ashift de ZFS fue la medida para “siempre alinear a una potencia de dos”.
  4. Los sistemas de archivos copy-on-write hicieron costosa la semántica de sobrescritura parcial. Los sistemas tradicionales podían sobrescribir in-place; ZFS normalmente escribe bloques nuevos, lo que cambia el perfil de coste de las pequeñas actualizaciones.
  5. La compresión al volverse “lo suficientemente barata” cambió prioridades de ajuste. Las CPUs modernas hicieron práctica la compresión ligera, y los registros más grandes suelen comprimirse mejor (menos cabeceras, más redundancia por bloque).
  6. La proliferación de VMs creó una nueva clase de “archivos que se comportan como discos.” QCOW2/VMDK/raw son archivos, pero sus patrones de acceso se parecen a dispositivos de bloques aleatorios—recordsize debe seguir esa realidad.
  7. La latencia de los SSD hizo que el I/O pequeño fuese más factible, pero también más sensible. Cuando el medio es rápido, la sobrecarga (checksums, metadata, amplificación de escritura) se convierte en una fracción mayor del tiempo total.
  8. ARC hizo la caché más estratégica que “más RAM es más rápido.” Cachear fragmentos de 128K para lecturas aleatorias de 8K puede ser un desperdicio; cachear fragmentos de 16K para streaming secuencial puede ser sobrecarga.

Cómo ZFS convierte tus escrituras en on-disk reality

Para ajustar recordsize responsablemente, necesitas un modelo mental de lo que ZFS hace con tus bytes.

Recordsize y la trampa de “lectura-modificación-escritura”

Supongamos que una app actualiza 8K en medio de un archivo grande. Si el bloque relevante en disco (registro) es 128K, ZFS no puede simplemente sobrescribir esos 8K “in place” como lo hacían los sistemas antiguos. Generalmente tiene que:

  1. Leer el viejo bloque de 128K (a menos que ya esté en ARC)
  2. Modificar la porción de 8K en memoria
  3. Escribir un nuevo bloque de 128K en otra parte (copy-on-write)
  4. Actualizar metadata para apuntar al nuevo bloque

Es una vista simplificada, pero la conclusión es: las escrituras aleatorias pequeñas dentro de registros grandes crean I/O extra y churn de asignación. En pools HDD, el I/O extra se convierte en dolor de seek. En pools SSD, se traduce en amplificación de escritura y jitter de latencia, especialmente bajo escrituras sync o alta concurrencia.

Por qué los registros más grandes pueden ser espectaculares para cargas secuenciales

Si tu carga lee y escribe rangos contiguos grandes—streams de backup, salidas de codificación de medios, archivos parquet, archivos de logs—los registros más grandes reducen la sobrecarga:

  • Menos punteros de bloque y operaciones de metadata
  • Menos peticiones de I/O para la misma cantidad de datos
  • Suele mejorar la eficiencia de compresión
  • Mejor comportamiento de prefetch: ZFS puede traer trozos significativos

En discos giratorios, esto es la diferencia entre throughput sostenido y un gráfico que parece un sismógrafo. En SSDs, es la diferencia entre saturar ancho de banda y saturar CPU/sobrecarga de IOPS primero.

Granularidad de ARC: cachear la “forma” correcta de los datos

ARC cachea bloques de ZFS. Si tu app hace lecturas aleatorias de 8K pero tus bloques son de 128K, cada fallo de caché trae 128K. Eso es “amplificación de lectura.” A veces ayuda (localidad espacial), pero para patrones verdaderamente aleatorios simplemente quema ARC y expulsa datos útiles más rápido.

Por el contrario, si tu carga es lecturas secuenciales grandes, un recordsize pequeño puede aumentar la sobrecarga: más bloques que gestionar, más operaciones de checksum, más búsquedas de metadata y prefetch menos eficiente.

Compresión y recordsize: aliados con límites

La compresión ocurre por bloque. Bloques más grandes suelen comprimirse mejor porque la redundancia es visible en una ventana mayor. Pero la compresión también significa que tu tamaño físico en disco puede ser menor que recordsize, lo que cambia la forma del I/O:

  • Con datos compresibles, un recordsize grande puede ser una victoria gratis: menos bloques, menos I/O físico.
  • Con datos incomprensibles, un recordsize grande principalmente cambia la granularidad de I/O y el coste de reescritura.
  • Con datos mixtos, verás una mezcla de tamaños físicos; el “máximo” todavía importa para la amplificación de reescritura.

Recordsize vs volblocksize (no los mezcles)

Los datasets almacenan archivos; los zvols exponen dispositivos de bloque. Para zvols, la perilla equivalente es volblocksize, que se establece al crear y es difícil de cambiar sin reconstruir el zvol. Un error común en producción es ajustar recordsize en un dataset que guarda imágenes VM dentro de un zvol—o ajustar volblocksize para archivos. Son capas diferentes.

Mapeo de cargas a recordsize (con números reales)

Aquí está el mapeo práctico que uso cuando estoy a cargo del pager.

Comparticiones de archivos generales (directorios home, archivos mixtos de oficina)

Acceso típico: documentos pequeños, descargas grandes ocasionales, muchas operaciones de metadata pequeñas. El 128K por defecto suele estar bien. Si tienes acceso aleatorio intenso dentro de archivos grandes (por ejemplo, grandes archivos tipo PST), considera reducirlo, pero no optimices a ciegas; mide primero.

Elección común: 128K. A veces 64K si hay mucho I/O aleatorio pequeño dentro de archivos grandes.

Bases de datos (Postgres, MySQL/InnoDB) almacenadas como archivos en datasets

La mayoría de las bases de datos hacen lecturas/escrituras de 8K–16K (varía por motor y configuración). Si almacenas archivos de datos de DB en un dataset (no en un zvol), un recordsize grande puede causar lectura-modificación-escritura e ineficiencia en ARC. Un recordsize cercano al tamaño de página de la BD suele ser mejor.

Elección común: 16K para Postgres (páginas de 8K pero 16K encaja con el comportamiento práctico de ZFS); 16K para muchas configuraciones InnoDB (a menudo páginas de 16K). A veces 8K para lecturas aleatorias con alta sensibilidad a latencia, pero vigila la sobrecarga.

Imágenes de disco VM como archivos (qcow2, raw, vmdk) en datasets

Este es el clásico “archivo que se comporta como disco.” El sistema invitado hace escrituras aleatorias de 4K; el hipervisor escribe en un archivo. Un recordsize grande te castigará con amplificación de escritura y fragmentación. Quieres bloques más pequeños. Muchas organizaciones se quedan en 16K o 32K como compromiso; 8K puede ser apropiado pero aumenta la sobrecarga de metadata.

Elección común: 16K o 32K.

Destinos de backup, archivos de medios, archivos de logs

Suelen ser secuenciales, en streaming y con muchas operaciones de append, normalmente sin reescritura in-place. Más grande es mejor: reduce sobrecarga, aumenta throughput de streaming y mejora la compresión. Para OpenZFS moderno, 1M de recordsize es una elección legítima para estos datasets—cuando la carga realmente es I/O secuencial grande.

Elección común: 1M (o 512K si quieres un punto intermedio más seguro).

Imágenes de contenedores y caches de build

Estos pueden ser desagradables: muchos archivos pequeños y metadata, pero también capas grandes. Ajustar solo recordsize no lo resolverá; también te importarán vdevs especiales para metadata, compresión y quizá atime=off. Para recordsize, el valor por defecto suele funcionar; si la carga está dominada por muchas lecturas aleatorias pequeñas dentro de blobs grandes, considera 64K, pero mide.

Elección común: 128K, a veces 64K.

Archivos analíticos (parquet, ORC, datos columnarios)

A menudo se leen en trozos grandes pero con cierto skipping. Si tus consultas escanean rangos amplios, registros mayores ayudan. Si haces muchas “leer un poco de muchos sitios”, registros más pequeños pueden reducir la amplificación de lectura. Esto depende mucho de la carga; comienza con 1M para escaneo puro y ajusta.

Elección común: 512K–1M para cargas de escaneo; 128K–256K para mixtas.

Broma #2 (corta, relevante): En almacenamiento, “una talla sirve para todos” es como “un timeout sirve para todo”—solo es verdad hasta tu primera revisión de incidentes.

Tres mini-historias del mundo corporativo

1) Incidente causado por una suposición errónea: “Es un archivo, así que recordsize=1M será más rápido”

Tenían un clúster de virtualización que guardaba discos VM como archivos raw en un dataset ZFS. Alguien leyó un blog sobre cómo recordsize grande mejora el throughput para backups y decidió que el dataset de VM debía ser recordsize=1M. Sonaba razonable: bloques más grandes, menos I/O, más velocidad.

El cambio se desplegó durante una ventana de mantenimiento rutinaria. La primera advertencia no fue un benchmark; fue el helpdesk. “Las VMs Windows se sienten lentas.” Luego el monitoreo se iluminó: IO wait elevado, colas creciendo y un patrón extraño—las lecturas parecían estar bien, pero las escrituras tenían jitter. Los gráficos del hipervisor mostraban ráfagas: un montón de escrituras pequeñas se convirtieron en actividad de escritura backend mucho mayor.

Al investigar, el pool ZFS realizaba mucha lectura-modificación-escritura. La carga era el comportamiento típico de VMs: escrituras aleatorias pequeñas, actualizaciones de metadata de sistema de archivos dentro de los invitados, journaling y syncs periódicos. Con registros de 1M, cada pequeña actualización podía reescribir un bloque enorme (o al menos desencadenar patrones de asignación de bloques grandes). Las tasas de acierto de ARC no los salvaban; el working set era más grande que la RAM y el patrón aleatorio hacía la caché menos efectiva.

La resolución no fue heroica. Crearon un dataset nuevo con recordsize=16K, migraron las imágenes VM con una copia que forzó la reescritura de bloques, y los síntomas desaparecieron en gran parte. La lección del postmortem fue clara: “archivo” no es una descripción de la carga. Si tu “archivo” es un disco virtual, trátalo como I/O de bloque.

El beneficio posterior fue cultural: añadieron un paso ligero de revisión de diseño de almacenamiento para nuevos datasets. No una burocracia—solo una lista de verificación de diez minutos: patrón de acceso, tamaños de I/O esperados, comportamiento sync y una elección explícita de recordsize.

2) Optimización que salió mal: reducir recordsize por doquier para bajar latencia

Otra organización tuvo un incidente de latencia en un sistema de base de datos y sacó la conclusión simple: “Bloques más pequeños son más rápidos.” Es comprensible cuando te han quemado la amplificación de escritura. Así que estandarizaron recordsize=8K en la mayoría de los datasets: bases de datos, directorios home, almacenamiento de artefactos, backups, todo.

Los informes del mes siguiente fueron raros. La latencia de la BD mejoró un poco, claro. Pero el sistema de backups empezó a fallar sus ventanas. La canalización de procesamiento de medios tardó más en leer y escribir archivos grandes. La utilización de CPU en nodos de almacenamiento subió, y no porque los usuarios estuvieran felices. Era sobrecarga: más bloques, más churn de metadata, más trabajo de checksum, más operaciones de I/O para mover la misma cantidad de datos.

Luego vino el golpe real: su dispositivo L2ARC parecía “ocupado” pero no mejoraba resultados. Recordsize pequeño significaba más entradas en la caché y presión de metadata. Aumentó la sobrecarga de gestión del working set, y la caché se volvió menos eficiente para las cargas de streaming que antes se beneficiaban de registros grandes y prefetch.

La solución fue dejar de tratar recordsize como una perilla global. Dividieron datasets por clase de carga: 16K para archivos DB, 128K para shares generales, 1M para backups y grandes artefactos. El resultado no fue solo rendimiento—fue previsibilidad. Los sistemas que hacen streaming recuperaron su throughput; los que hacen I/O aleatorio redujeron su amplificación; y la CPU de almacenamiento dejó de pretender que era un minero de criptomonedas.

La lección que quedó: un recordsize “universal” es un compromiso universal, y los compromisos son donde nacen los incidentes.

3) Una práctica aburrida pero correcta que salvó el día: dataset por carga con disciplina de migración

Aquí tienes la clase de historia que nadie presume, pero es la razón por la que algunos equipos duermen tranquilos.

Esta compañía ejecutaba un pool ZFS compartido para múltiples servicios internos: una flota PostgreSQL, un servicio NFS de directorios home y un repositorio de backups. Hace años, adquirieron la costumbre de crear un dataset por clase de carga con propiedades explícitas: recordsize, compresión, atime, política de sync y convenciones de mountpoint. No era elegante. Era consistente.

Cuando llegó un nuevo servicio de analytics—grandes archivos columnarios, lecturas mayormente secuenciales—el equipo de almacenamiento no tocó los datasets existentes. Crearon un dataset nuevo con recordsize=1M y compresión apropiada, luego incorporaron el servicio allí. El rendimiento fue bueno desde el primer día, en gran parte porque no forzaron una carga analítica a vivir en un dataset sintonizado para directorios home.

Meses después, una regresión en la canalización analítica hizo que empezara a realizar más lecturas aleatorias. La latencia subió. El SRE on-call hizo un diagnóstico rápido: confirmó el cambio de patrón I/O, inspeccionó tamaños de bloque, revisó comportamiento de ARC. Redujeron recordsize moderadamente (de 1M a 256K) en una ejecución en staging, validaron con consultas realistas y luego desplegaron una migración controlada que reescribió los datos calientes. Nada de cirugía de emergencia en producción. Nada de “cambiemos la configuración del pool y crucemos los dedos.”

El día lo salvó el aburrimiento: datasets con nombres claros, propiedades explícitas y la memoria muscular para “si cambias recordsize, planifica cómo reescribir los datos.” El ticket nunca llegó a ser una llamada de incidente. Ese es el mejor tipo de éxito.

Tareas prácticas: comandos, salidas e interpretación

Las siguientes tareas están escritas como las ejecutaría en un host Linux con OpenZFS. Ajusta nombres de pool/dataset según sea necesario. Cada comando incluye qué buscar para que puedas tomar una decisión, no solo generar salida.

Tarea 1: Listar datasets y recordsize

cr0x@server:~$ zfs list -o name,used,avail,recordsize,compression,mountpoint
NAME                     USED  AVAIL  RECORDSIZE  COMPRESS  MOUNTPOINT
tank                     3.21T  7.80T     128K     lz4       /tank
tank/vm                  1.40T  7.80T      16K     lz4       /tank/vm
tank/db                  820G   7.80T      16K     lz4       /tank/db
tank/backups             900G   7.80T       1M     zstd      /tank/backups

Interpretación: verifica que los datasets coincidan con las clases de carga. Si tu dataset de VM dice 1M, probablemente encontraste tu próximo ticket de rendimiento.

Tarea 2: Ver una propiedad de dataset individual (y valores heredados)

cr0x@server:~$ zfs get -o name,property,value,source recordsize tank/vm
NAME      PROPERTY    VALUE  SOURCE
tank/vm   recordsize  16K    local

Interpretación: si SOURCE es inherited, confirma que el dataset padre esté intencionadamente configurado. La herencia accidental es común en entornos de “arreglo rápido”.

Tarea 3: Cambiar recordsize de forma segura (afecta solo escrituras nuevas)

cr0x@server:~$ sudo zfs set recordsize=16K tank/vm
cr0x@server:~$ zfs get -o name,property,value,source recordsize tank/vm
NAME      PROPERTY    VALUE  SOURCE
tank/vm   recordsize  16K    local

Interpretación: esto no reescribe bloques existentes. Si necesitas impacto inmediato, planifica una reescritura/migración (ver tareas posteriores).

Tarea 4: Inspeccionar tamaños de bloque reales usados por un archivo existente

cr0x@server:~$ sudo zdb -bbbbb tank/vm | head -n 20
Dataset tank/vm [ZPL], ID 56, cr_txg 124567, 1.40T, 1045320 objects

    Object  lvl   iblk   dblk   dsize  dnsize  lsize   %full  type
        96    1   128K    16K   1.40T   512B   1.40T   100%  ZFS plain file

Interpretación: mira dblk (tamaño de bloque de datos). Si tu dataset tiene recordsize 16K pero los archivos existentes muestran 128K, fueron escritos antes del cambio o por un patrón de carga distinto.

Tarea 5: Comprobar ratio de compresión y espacio lógico vs físico

cr0x@server:~$ zfs get -o name,property,value -H compressratio,logicalused,used tank/backups
tank/backups	compressratio	1.82x
tank/backups	logicalused	1.63T
tank/backups	used	900G

Interpretación: si la compresión está ayudando, un recordsize mayor puede amplificar ese beneficio para cargas secuenciales. Si compressratio es ~1.00x en datos incomprensibles, la elección de recordsize importa principalmente para la forma de I/O y el coste de reescritura.

Tarea 6: Medir I/O aleatorio vs secuencial rápidamente con fio (sanity check)

cr0x@server:~$ fio --name=randread --directory=/tank/vm --rw=randread --bs=4k --iodepth=32 --numjobs=4 --size=4G --time_based --runtime=30 --group_reporting
randread: (groupid=0, jobs=4): err= 0: pid=18432: Thu Dec 24 12:10:33 2025
  read: IOPS=38.2k, BW=149MiB/s (156MB/s)(4468MiB/30001msec)
    slat (usec): min=3, max=240, avg=12.7, stdev=4.9
    clat (usec): min=60, max=3900, avg=820.2, stdev=210.5

Interpretación: ejecuta esto en el dataset que coincida con tu carga. Si tu app real hace I/O aleatorio de 4K/8K y la latencia es mala, recordsize puede ser demasiado grande (o la configuración de sync/dispositivo de log limita). Usa fio para confirmar la forma de I/O primero.

Tarea 7: Observar I/O del pool y latencia bajo carga real

cr0x@server:~$ iostat -x 1
Linux 6.8.0 (server)  12/24/2025  _x86_64_  (32 CPU)

avg-cpu:  %user %nice %system %iowait  %steal  %idle
           9.2   0.0     6.1    18.7     0.0   66.0

Device            r/s     w/s   rMB/s   wMB/s  avgrq-sz avgqu-sz await  svctm  %util
nvme0n1         420.0   980.0    52.0   120.0    180.2      4.6   3.6   0.4   54.0
nvme1n1         410.0   970.0    51.3   119.1    179.6      4.8   3.8   0.4   55.0

Interpretación: si await sube con I/O pequeño, puedes estar golpeando latencia de escritura sync, saturación de dispositivo o amplificación de escritura. Recordsize es sospechoso cuando la tasa de escritura del pool parece alta respecto a la tasa de escritura de la aplicación.

Tarea 8: Ver I/O a nivel ZFS con zpool iostat

cr0x@server:~$ zpool iostat -v 1
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        3.21T  7.80T  8.10K  12.5K   210M   380M
  mirror                    1.60T  3.90T  4.05K  6.20K   105M   190M
    nvme0n1                    -      -  2.02K  3.10K  52.5M  95.0M
    nvme1n1                    -      -  2.03K  3.10K  52.5M  95.0M
--------------------------  -----  -----  -----  -----  -----  -----

Interpretación: compara el throughput de la aplicación con el ancho de banda del pool. Si el pool escribe mucho más de lo que la app reporta, estás viendo amplificación (desajuste de recordsize, comportamiento sync, churn de metadata o fragmentación).

Tarea 9: Comprobar estadísticas de ARC para señales de efectividad de caché

cr0x@server:~$ arcstat 1 5
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c
12:11:10  185K  21.2K   11     9K   4  10K    5   2K    1   58G   64G
12:11:11  192K  30.1K   15    14K   7  12K    6   4K    2   58G   64G

Interpretación: un aumento de miss% durante una carga que debería ser cache-friendly puede significar que los bloques son demasiado grandes para el patrón de acceso (o que el working set es demasiado grande). Recordsize puede ser parte de esa historia.

Tarea 10: Confirmar el tamaño de I/O de la carga (antes de afinar)

cr0x@server:~$ sudo pidstat -d 1 5 -p 12345
Linux 6.8.0 (server)  12/24/2025  _x86_64_  (32 CPU)

12:12:01      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
12:12:02     1001     12345    5120.0    2048.0     128.0  postgres

Interpretación: esto muestra tasas, no tamaños, pero es tu primera pista sobre dominancia de lectura/escritura. Combínalo con conocimiento de la app (tamaño de página de BD, tamaños de bloque VM). Si no puedes describir tus tamaños de I/O, estás afinando a ciegas.

Tarea 11: Forzar la reescritura de un archivo para aplicar el nuevo recordsize

Esta es la parte operativa útil: cambiar recordsize no reescribe bloques existentes. Para aplicarlo a archivos existentes, necesitas reescribir el contenido del archivo. Un método simple es “copiar fuera y volver a copiar”, idealmente dentro del mismo pool pero a un dataset nuevo con las propiedades deseadas.

cr0x@server:~$ sudo zfs create -o recordsize=16K -o compression=lz4 tank/vm_new
cr0x@server:~$ sudo rsync -aHAX --inplace --progress /tank/vm/ /tank/vm_new/
cr0x@server:~$ sudo zfs set mountpoint=/tank/vm_old tank/vm
cr0x@server:~$ sudo zfs set mountpoint=/tank/vm tank/vm_new

Interpretación: rsync fuerza que se asignen nuevos bloques con las propiedades del dataset nuevo. El intercambio de mountpoint es el corte “aburrido pero seguro”. Prueba los servicios antes de borrar el dataset viejo.

Tarea 12: Validar que los datos reescritos usan el nuevo tamaño de bloque

cr0x@server:~$ sudo zdb -bbbbb tank/vm_new | head -n 20
Dataset tank/vm_new [ZPL], ID 102, cr_txg 124890, 1.39T, 1039120 objects

    Object  lvl   iblk   dblk   dsize  dnsize  lsize   %full  type
        96    1   128K    16K   1.39T   512B   1.39T   100%  ZFS plain file

Interpretación: dblk debería alinearse con tu recordsize previsto para datos recién escritos. Si no es así, investiga: ¿los archivos son sparse? ¿estás tratando con zvols? ¿la carga está escribiendo en bloques más grandes de lo esperado?

Tarea 13: Ver comportamiento de “archivos calientes” con filefrag

cr0x@server:~$ sudo filefrag -v /tank/vm/disk01.raw | head -n 15
Filesystem type is: 0x2fc12fc1
File size of /tank/vm/disk01.raw is 107374182400 (26214400 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..     255:   1200000..  1200255:    256:             last,eof
   1:      256..     511:   1310000..  1310255:    256:   1200256:

Interpretación: la fragmentación no es puramente un problema de recordsize, pero el desajuste (registros grandes con escrituras aleatorias) aumenta el churn de asignación y puede empeorar los patrones. Si ves fragmentación extrema más cargas de escritura aleatoria, los registros más pequeños y mayor espacio libre pueden ayudar.

Tarea 14: Comprobar señales de comportamiento de escrituras sync (no culpes a recordsize por problemas de slog)

cr0x@server:~$ zfs get -o name,property,value,source sync tank/db
NAME     PROPERTY  VALUE  SOURCE
tank/db  sync      standard  inherited

Interpretación: si tienes escrituras sync de alta latencia (las bases de datos a menudo las tienen), ajustar recordsize no arreglará un SLOG lento o la ausencia de un dispositivo de log separado. Diagnostica el path sync por separado; recordsize ayuda con la amplificación de reescritura, no con “mi dispositivo de sync es lento”.

Guía de diagnóstico rápido

Este es el plan “qué reviso primero, segundo, tercero” cuando el rendimiento falla y sospechas que la afinación de ZFS puede estar implicada.

1) Confirma el patrón de I/O de la carga antes de tocar ZFS

  • ¿Es aleatorio o secuencial?
  • Tamaño típico de I/O: 4K, 8K, 16K, 128K, 1M?
  • ¿Lectura-dominante o escritura-dominante? ¿Sync-dominante?

Ejecuta observación básica: métricas de la app, iostat -x, zpool iostat. Si el patrón es escrituras aleatorias pequeñas y tu dataset está afinado para bloques enormes secuenciales, probablemente encontraste la pistola humeante.

2) Revisa propiedades del dataset y herencia

  • zfs get recordsize,compression,atime,primarycache,logbias,sync
  • Confirma que estás en un dataset (archivos) vs un zvol (dispositivo de bloque)

Si el dataset hereda de un padre “catch-all”, verifica que el padre no fuera sintonizado para una carga distinta hace seis meses durante un pánico.

3) Verifica qué hay realmente en disco (no lo que esperas)

  • zdb -bbbbb pool/dataset para tamaños de bloque
  • Ratio de compresión y uso lógico vs físico

Este paso evita la trampa común: cambiaste recordsize la semana pasada pero nunca reescribiste los datos calientes, así que en la práctica nada cambió.

4) Busca amplificación y cuellos de botella que se hacen pasar por problemas de recordsize

  • El pool escribe mucho más que las escrituras de la aplicación (amplificación)
  • Alta latencia de escrituras sync (SLOG o latencia de vdev)
  • Misses de ARC por bloques sobredimensionados para lecturas aleatorias
  • Saturación de CPU por checksums/compresión en tamaños de bloque muy pequeños

5) Haz un cambio reversible, luego aplícalo correctamente

Cambiar recordsize es reversible, pero “sentir” el beneficio requiere reescribir datos. Planifica una migración (dataset nuevo, rsync, cutover) en lugar de ajustar la propiedad y esperar resultados.

Errores comunes: síntomas y soluciones

Error 1: Usar recordsize enorme para imágenes VM

Síntomas: VMs se bloquean durante actualizaciones, ráfagas periódicas de escritura, escrituras de almacenamiento muy superiores a las del invitado, picos de latencia bajo carga concurrente.

Solución: pon imágenes VM en un dataset afinado para I/O aleatorio: recordsize=16K o 32K. Migra/re-escribe las imágenes para que los bloques existentes se recreeen con el nuevo tamaño.

Error 2: Usar recordsize tiny para backups y medios

Síntomas: trabajos de backup no alcanzan objetivos de throughput, CPU alta en nodos de almacenamiento, muchas IOPS pero pocos MB/s, prefetch parece inefectivo.

Solución: crea un dataset de backup/medios con recordsize=1M (o 512K), compresión adecuada (a menudo zstd o lz4), y reescribe/migra los flujos de datos entrantes.

Error 3: Cambiar recordsize y esperar mejora inmediata

Síntomas: “Pusimos recordsize a 16K y no cambió nada.” Benchmarks y comportamiento de la app siguen igual.

Solución: reescribe los datos calientes. Para archivos grandes existentes necesitas copiar/rsync a un dataset nuevo, o una operación controlada que fuerce nueva asignación de bloques.

Error 4: Confundir recordsize con volblocksize

Síntomas: ajustas recordsize, pero la carga está en un zvol (iSCSI/LUN). Sin impacto medible; confusión en la revisión de incidentes.

Solución: para zvols, planifica volblocksize al crear. Si está mal, la solución limpia suele ser “nuevo zvol con volblocksize correcto, migrar datos.” No luches contra la física con propiedades deseadas.

Error 5: Ignorar comportamiento de escrituras sync y culpar a recordsize

Síntomas: commits de base de datos lentos, pero lecturas están bien; la latencia se correlaciona con fsync/commit; cambiar recordsize no ayuda.

Solución: evalúa el path sync: calidad del dispositivo de log separado (SLOG), latencia de los vdevs principales, expectativas de sync=standard y configuraciones de la aplicación. Recordsize ayuda con amplificación de reescritura, no con “mi dispositivo de sync es lento”.

Error 6: Afinar recordsize sin considerar la compresión

Síntomas: uso de CPU impredecible, o tasas físicas de escritura sorprendentemente diferentes tras cambios.

Solución: si usas compresión, entiende que los tamaños físicos de bloque pueden reducirse; mide compressratio, capacidad CPU y ancho de banda del pool. Un recordsize mayor puede mejorar compresión en algunos datos, pero también aumentar el coste de reescritura para actualizaciones aleatorias.

Listas de verificación / plan paso a paso

Checklist A: Elegir recordsize para un dataset nuevo

  1. Nombrar la carga. “Imágenes VM”, “Postgres”, “streams de backup”, “home dirs mixtos”. No “archivos”.
  2. Identificar tamaño típico de I/O. 4K/8K/16K/128K/1M. Usa docs de la app o medición.
  3. Decidir si las sobreescrituras son comunes. Bases de datos y VMs sobrescriben; backups mayormente append.
  4. Elegir un recordsize inicial. Puntos de partida comunes: 16K (BD/VM), 128K (general), 1M (backups/medios).
  5. Configurar compresión intencionalmente. lz4 suele ser un defecto seguro; compresión más agresiva debe justificarse.
  6. Documentarlo en las propiedades del dataset. No dependas de la memoria tribal. Al menos: recordsize, compresión, atime, expectativas de sync.

Checklist B: Cambiar recordsize en una carga existente (enfoque operativo seguro)

  1. Confirma que estás afinando la capa correcta. Dataset vs zvol.
  2. Mide comportamiento actual. Baseline: latencia, throughput, ancho de banda del pool, miss% de ARC. Captura antes/después.
  3. Configura recordsize en un dataset nuevo. Evita cambiar el dataset en vivo primero si los datos necesitan reescritura de todos modos.
  4. Migra con reescritura. Usa rsync/copy de forma que se asignen nuevos bloques (copiar a dataset nuevo).
  5. Corta con intercambio de mountpoint o cambio de configuración del servicio. Facilita la reversión.
  6. Valida tamaños de bloque y rendimiento. Usa zdb y pruebas de carga.
  7. Mantén el dataset viejo temporalmente. Seguro para rollback; borrarlo después cuando la confianza sea alta.

Checklist C: “¿Es recordsize siquiera el problema?”

  1. Si los picos de latencia se correlacionan con escrituras sync, investiga SLOG/latencia de dispositivo primero.
  2. Si el throughput es bajo pero la CPU es alta, revisa sobrecarga por tamaños de bloque minúsculos o compresión pesada.
  3. Si el ancho de banda del pool es enorme comparado con las escrituras de la app, sospecha amplificación (desajuste de recordsize, fragmentación, churn de metadata).
  4. Si el rendimiento empeora al llenar el pool, revisa fragmentación y espacio libre; recordsize puede empeorar síntomas pero no suele ser la causa raíz.

Preguntas frecuentes

1) ¿Cambiar recordsize reescribe datos existentes?

No. Afecta a las escrituras nuevas. Los bloques existentes mantienen su tamaño antiguo hasta que las regiones del archivo se reescriben. Si necesitas beneficio inmediato, migra/reescribe los datos calientes.

2) ¿Cuál es el mejor recordsize para PostgreSQL?

Comúnmente 16K en un dataset que contiene archivos de PostgreSQL, porque tiende a comportarse bien con el I/O típico de BD manteniendo la amplificación de reescritura razonable. Pero mide: la carga, el tamaño de ARC/RAM y si estás limitado por sync importan más que los eslóganes.

3) ¿Cuál es el mejor recordsize para imágenes VM almacenadas como archivos?

Típicamente 16K o 32K. El I/O de VM suele ser pequeño y aleatorio; un recordsize enorme suele causar amplificación. Si tu entorno es extremadamente sensible a IOPS, 8K puede funcionar, pero incrementa la sobrecarga y puede reducir el throughput secuencial de operaciones como escaneos completos de disco en el invitado.

4) ¿Debería poner recordsize a 1M para todo porque “es más rápido”?

No. Es más rápido para streaming secuencial grande y a menudo peor para sobrescrituras aleatorias pequeñas. Un recordsize de 1M en datasets de VM o BD es causa común de tickets “¿por qué las escrituras son tan espasmódicas?”.

5) ¿Cómo cambia la compresión las decisiones sobre recordsize?

La compresión es por bloque. Bloques más grandes pueden comprimirse mejor, lo que reduce I/O físico para datos compresibles. Pero el máximo de recordsize aún controla el ámbito de reescritura: un pequeño cambio dentro de un registro grande puede seguir disparando trabajo de reescritura grande, incluso si el resultado comprimido es pequeño.

6) ¿Recordsize más pequeño siempre es mejor para latencia?

No. Bloques más pequeños pueden reducir la amplificación de lectura para lecturas aleatorias y reducir el tamaño de reescritura para actualizaciones pequeñas, pero también aumentan la sobrecarga de metadata y trabajo de CPU. Para cargas secuenciales, un recordsize demasiado pequeño puede reducir throughput e incrementar consumo de CPU.

7) ¿Cómo sé qué tamaños de bloque se usan realmente?

Usa zdb -bbbbb pool/dataset para inspeccionar tamaños de bloque por dataset/objeto y valida con chequeos puntuales tras una migración. No asumas que cambiar recordsize modifica archivos existentes.

8) ¿Cuál es la diferencia entre recordsize y ashift?

ashift es una configuración de alineación de sector a nivel de pool/vdev (típicamente 12 para 4K, 13 para 8K). Afecta la granularidad mínima de asignación y la alineación. recordsize es un tamaño máximo de bloque a nivel de dataset. Interactúan—la desalineación puede causar amplificación—pero resuelven problemas distintos.

9) ¿Puede recordsize causar fragmentación?

Recordsize no “causa” fragmentación por sí mismo, pero un desajuste (registros grandes con sobrescrituras aleatorias) incrementa el churn de asignación y puede empeorar la fragmentación con el tiempo. Mantener espacio libre saludable y emparejar recordsize con patrones de I/O ayuda.

10) ¿Debo afinar recordsize para shares NFS?

Afina para el comportamiento de la aplicación detrás de NFS. NFS es transporte; la verdadera pregunta es si los clientes hacen I/O aleatorio pequeño dentro de archivos grandes (proyectos CAD, imágenes VM, archivos BD) o mayormente cargas secuenciales/append.

Conclusión

Si quieres una única palanca de afinación en ZFS que cambie resultados de forma fiable, recordsize es esa—porque determina la granularidad de los bloques de datos, y la granularidad lo decide todo: coste de reescritura, eficiencia de caché, tasa de peticiones I/O y qué tan bien manejas acceso aleatorio vs secuencial.

El truco operativo es dejar de tratar recordsize como un ajuste y empezar a tratarlo como una elección de diseño. Crea datasets por carga de trabajo, elige recordsize según patrones de I/O y recuerda la parte que todos olvidan: cambiarlo solo importa si reescribes los datos. Haz eso, y tendrás menos misterios de rendimiento y más gráficos aburridos—exactamente el tipo de aburrimiento que mantiene viva la producción.

← Anterior
WordPress «Error al establecer una conexión con la base de datos»: recuperación rápida y prevención
Siguiente →
Diseños de refrigeración: 2 ventiladores vs 3 ventiladores—qué cambia realmente

Deja un comentario