Cambiaste recordsize en un dataset ZFS y nada se aceleró. O peor: algo se volvió más lento y ahora miras los paneles como si te debieran dinero.
Bienvenido al momento más común de “pero ajusté la perilla” en operaciones ZFS.
La estrategia de recordsize es uno de esos temas donde la mitad del consejo es correcto, un cuarto está obsoleto y el resto está equivocado con seguridad. El truco es aprender
qué cambia al instante, qué solo afecta a los bloques escritos después del cambio y cómo migrar sin volar todo el sistema de archivos solo para satisfacer un parámetro.
Qué hace realmente recordsize (y qué no hace)
recordsize es el tamaño máximo de bloque de datos que ZFS usa para archivos en un dataset tipo filesystem. Esa palabra “máximo” es toda la historia.
ZFS almacenará un archivo en bloques de hasta recordsize, pero puede (y lo hará) usar bloques más pequeños cuando el archivo es pequeño, cuando la aplicación escribe
en trozos pequeños, o cuando no puede empaquetar datos eficientemente en registros completos.
Aquí está la verdad operacional innegociable: cambiar recordsize no reescribe los bloques existentes.
Cambia cómo se disponen los datos nuevos a partir de ese momento. Los bloques antiguos permanecen del tamaño que tuvieron cuando se escribieron.
Por eso “cambiamos recordsize y no ayudó” no es un misterio; es comportamiento esperado.
Recordsize trata de equilibrar eficiencia secuencial frente al coste de actualizaciones aleatorias. Los registros grandes son ideales cuando haces streaming de archivos grandes (backups, multimedia, logs que solo se anexan, grandes stores de objetos).
Los registros pequeños son mejores cuando haces lecturas/escrituras aleatorias pequeñas (algunas bases de datos, imágenes de VM, buzones de correo, cargas de trabajo con mucha metadata).
El error es pensar que existe un único valor correcto. No existe. Existe un valor correcto por dataset y por carga de trabajo.
Además: recordsize es para filesystems (zfs datasets). Para dispositivos de bloque (zvol), se usa volblocksize, que se establece al crear y no se cambia a la ligera.
Si ejecutas VMs sobre zvols y estás afinando recordsize… estás puliendo una bicicleta para mejorar la economía de combustible de un camión.
Cómo interactúa recordsize con el mundo real
- Amplificación de IO: actualizar 4K en medio de un registro de 128K puede costar más que 4K. ZFS usa copy-on-write, checksums y compresión; puede necesitar reescribir mucha estructura para cambiar poco dato.
- Comportamiento del ARC: los bloques grandes pueden consumir ARC en porciones mayores; eso puede ser bueno (menos búsquedas de metadata) o malo (rotación de caché) según patrones de acceso.
- Compresión: los registros más grandes a menudo comprimen mejor. Pero comprimir un registro de 1M para ahorrar espacio no es “gratis” cuando tu carga hace lecturas aleatorias de rangos pequeños.
- Special vdevs: si tienes clases de asignación especiales para metadata/bloques pequeños, los cambios de recordsize pueden desplazar qué califica como “pequeño” y terminar en tu special vdev. Eso puede ser un regalo o un incendio.
- Snapshots: reescribir datos para adoptar un nuevo recordsize mantendrá los bloques antiguos vivos vía snapshots. Los planes de migración que ignoran la retención de snapshots son cómo los equipos de almacenamiento aprenden humildad.
Una cita para mantenerte honesto
Idea parafraseada (Gene Kim): “Mejorar la fiabilidad viene de entender el flujo y los bucles de retroalimentación, no de heroicidades tras un incidente.”
Hechos interesantes y contexto histórico
ZFS lleva el suficiente tiempo como para acumular folklore. Parte es útil; otra parte debería compostarse. Aquí algunos hechos concretos y puntos de contexto
que realmente importan para decisiones sobre migración de recordsize:
- Recordsize nació en la era Solaris como un pragmático “lo suficientemente grande para secuencial, no demasiado para memoria”; 128K se volvió común porque funcionaba en discos reales y cargas reales.
- El recordsize por defecto permaneció estable históricamente precisamente porque es un compromiso, no porque sea óptimo. Está diseñado para ser “aceptable” para uso compartido general, no “ideal” para tu base de datos OLTP.
- ZFS siempre soportó tamaños de bloque variables hasta recordsize; por eso los archivos pequeños no se rellenan hasta 128K. Esto también explica por qué recordsize es un tope, no un mandato.
- OpenZFS divergidó y evolucionó entre plataformas. Características como dnodes grandes, special vdevs y prefetch mejorado cambiaron el panorama de rendimiento alrededor de recordsize, aunque recordsize en sí no “cambió”.
- Los algoritmos de compresión mejoraron (por ejemplo, LZ4 se volvió el predeterminado práctico). Eso hizo que “registros más grandes comprimen mejor” fuera más relevante operativamente porque la compresión se volvió más barata de usar en todas partes.
- Las unidades con sectores de 4K empujaron las realidades de ashift. Desajustar ashift con los sectores físicos puede dominar el rendimiento; la afinación de recordsize no compensa una alineación física mala.
- Tendencias de almacenamiento de VM exigieron claridad: la comunidad aprendió (a veces en voz alta) que la afinación de zvol es distinta a la de filesystem, y los consejos sobre recordsize no se transfieren directamente.
- SSD/NVMe cambiaron los modos de fallo: las IOPS aleatorias son menos aterradoras que en discos giratorios, pero la amplificación de escritura y los picos de latencia todavía importan—especialmente con RAID de paridad, pequeñas actualizaciones y escrituras sincrónicas.
Por qué estás migrando: motivaciones basadas en síntomas
“Queremos cambiar recordsize” no es un objetivo. Es una táctica. Tu objetivo suele ser uno de estos:
- Reducir latencia en lecturas o escrituras aleatorias para aplicaciones que tocan regiones pequeñas.
- Aumentar el rendimiento para flujos secuenciales grandes (trabajos de backup, procesamiento de medios, ETL, archivado).
- Reducir la amplificación de escritura en cargas con muchas sobreescrituras (algunos patrones de base de datos, imágenes de VM, capas de contenedores con churn).
- Arreglar la rotación de caché donde ARC se llena con registros grandes pero la carga necesita porciones pequeñas.
- Evitar sobrecargar un special vdev porque demasiados bloques califican como “pequeños”.
- Hacer que la replicación y send/receive sean predecibles alineando las escrituras nuevas con una política de bloques conocida.
Migras la estrategia de recordsize cuando has medido una discordancia entre lo que hace tu carga y para qué está optimizado tu dataset.
Si lo haces porque “alguien en internet dijo que las bases de datos prefieren 16K,” ya estás a medio camino hacia un informe de incidente.
Guía rápida de diagnóstico
Cuando el rendimiento es malo y se culpa a recordsize (a menudo injustamente), no comiences reescribiendo datos.
Empieza por probar qué está lento y dónde.
Primero: identifica el patrón de carga
- ¿Son principalmente lecturas aleatorias? ¿Escrituras aleatorias? ¿Lecturas/escrituras secuenciales?
- ¿Qué tamaño de IO emite realmente la aplicación? ¿4K? ¿16K? ¿128K? ¿Mixto?
- ¿La lentitud es por latencia o por ancho de banda?
Segundo: revisa la ruta de almacenamiento por “límites duros”
- ¿Las escrituras sync están esperando en SLOG? (O peor: sin SLOG en un sistema con muchas escrituras sync.)
- ¿vdevs de paridad haciendo pequeñas sobreescrituras aleatorias? Ese es un cobrador de impuestos que no duerme.
- ¿Special vdev saturado? La IO de metadata puede paralizar todo el pool.
- ¿Ashift incorrecto? La desalineación puede dominar todo lo demás.
Tercero: valida qué está haciendo recordsize hoy
- ¿Cuál es el recordsize actual del dataset?
- ¿Qué tamaños de bloque existen en disco para los archivos calientes?
- ¿Hay snapshots manteniendo diseños antiguos para que la “migración” no cambie la realidad física?
Recordsize es una palanca. Pero no la actúes hasta confirmar que es la correcta.
Tareas prácticas: comandos, salidas y decisiones
Estas son tareas operativas reales que puedes ejecutar hoy. Cada una incluye un comando, una salida de ejemplo, qué significa y la decisión que tomas a partir de ello.
Sin magia, sin intuiciones.
Task 1: Confirmar recordsize actual (y si estás mirando el dataset correcto)
cr0x@server:~$ zfs get -o name,property,value,source recordsize tank/app
NAME PROPERTY VALUE SOURCE
tank/app recordsize 128K local
Significado: tank/app tiene recordsize 128K establecido localmente. Si aparece inherited, la configuración viene de un dataset padre.
Decisión: Si pretendías afinar solo un dataset hijo, detente y corrige los límites de herencia antes de cambiar nada.
Task 2: Verificar si estás en un filesystem o zvol
cr0x@server:~$ zfs list -o name,type,volblocksize,recordsize,mountpoint tank
NAME TYPE VOLBLOCKSIZE RECORDSIZE MOUNTPOINT
tank filesystem - 128K /tank
tank/app filesystem - 128K /tank/app
tank/vm-01 volume 16K - -
Significado: tank/vm-01 es un zvol; recordsize no aplica. volblocksize sí.
Decisión: Si tu problema es IO de VM y está en zvols, deja de afinar recordsize y evalúa volblocksize, alineación de zvol y patrones de IO del invitado.
Task 3: Revisar ashift y disposición de vdevs (recordsize no salva fundamentos malos)
cr0x@server:~$ zdb -C tank | egrep "ashift|vdev_tree|type:|path:"
ashift: 12
type: 'raidz'
path: '/dev/disk/by-id/nvme-SAMSUNG_MZQLB1T9HAJR-00007_...'
Significado: ashift=12 significa sectores 4K. Eso suele ser sensato. Si ves ashift=9 en discos modernos, asume dolor por desalineación.
Decisión: Si ashift es incorrecto, planifica reconstruir el pool. No pierdas una semana “afinando recordsize” como mecanismo de afrontamiento.
Task 4: Confirmar compresión y su interacción con el tamaño de IO
cr0x@server:~$ zfs get -o name,property,value compression,compressratio tank/app
NAME PROPERTY VALUE SOURCE
tank/app compression lz4 local
tank/app compressratio 1.43x -
Significado: LZ4 está habilitado; los datos se están comprimiendo razonablemente. Los registros más grandes suelen mejorar la relación de compresión, pero pueden aumentar la latencia en lecturas aleatorias pequeñas.
Decisión: Si eres sensible a la latencia y ves alta compressratio con registros grandes, considera si pagas coste de descompresión en lecturas de porciones diminutas.
Task 5: Observar patrones de tamaño de IO en tiempo real (¿adivinas o mides?)
cr0x@server:~$ iostat -x 1 5
avg-cpu: %user %nice %system %iowait %steal %idle
8.12 0.00 3.94 1.25 0.00 86.69
Device r/s w/s rMB/s wMB/s avgrq-sz await svctm %util
nvme0n1 820.0 610.0 12.8 9.6 32.0 1.9 0.4 58.0
Significado: avgrq-sz ~32K por petición. Eso no es “streaming grande.” Es IO de tamaño moderado. Si tu recordsize es 1M y haces peticiones de 32K, deberías sospechar.
Decisión: Si los tamaños de petición son pequeños y aleatorios, considera recordsize más pequeño (o acepta que la aplicación es el factor limitante, no tu dataset).
Task 6: Revisar IO y latencia a nivel ZFS con zpool iostat
cr0x@server:~$ zpool iostat -v tank 1 3
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
tank 2.10T 5.15T 1.20K 900 85.0M 60.0M
raidz1-0 2.10T 5.15T 1.20K 900 85.0M 60.0M
nvme0n1 - - 400 300 28.0M 20.0M
nvme1n1 - - 400 300 28.0M 20.0M
nvme2n1 - - 400 300 28.0M 20.0M
-------------------------- ----- ----- ----- ----- ----- -----
Significado: El pool está haciendo muchas operaciones pequeñas. Si las ops son altas pero el ancho de banda es modesto, probablemente estás limitado por IOPS/latencia, no por throughput.
Decisión: Si estás limitado por IOPS en RAIDZ, recordsize más pequeño podría reducir la amplificación read-modify-write en algunos patrones de sobreescritura, pero la sobrecarga de paridad puede seguir dominando. Considera mirrors para cargas con muchas escrituras aleatorias.
Task 7: Comprobar presión de escrituras sync (recordsize no arregla tormentas de fsync)
cr0x@server:~$ zpool get -o name,property,value,source sync,logbias tank
NAME PROPERTY VALUE SOURCE
tank sync standard default
tank logbias latency local
Significado: sync=standard es normal. logbias=latency prefiere el SLOG para escrituras sync (si existe).
Decisión: Si tu app hace muchas fsync y la latencia es mala, confirma salud/rendimiento del SLOG antes de tocar recordsize. Recordsize no hace más rápido un dispositivo de sync lento.
Task 8: Verificar que tengas (o no) un dispositivo SLOG
cr0x@server:~$ zpool status -v tank
pool: tank
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
nvme0n1 ONLINE 0 0 0
nvme1n1 ONLINE 0 0 0
nvme2n1 ONLINE 0 0 0
logs
nvme3n1 ONLINE 0 0 0
Significado: Hay un dispositivo de log separado. Bien. Ahora verifica que no sea un SSD consumidor con características PLP miserables.
Decisión: Si no tienes SLOG y tienes cargas sync, no conviertas a recordsize en chivo expiatorio.
Task 9: Inspeccionar tamaños de bloque de archivos en disco (¿tu “nuevo ajuste” está realmente presente?)
cr0x@server:~$ zdb -ddddd tank/app | egrep "Object|blksz|type" | head -n 12
Object lvl iblk dblk dsize dnsize bonustype bsize data
7 1 128K 128K 9.00M 512 DMU dnode 16K ZFS plain file
dblk [0] L0 128K 1.00M
dblk [1] L0 128K 1.00M
Significado: Los bloques de datos de este archivo son 128K. Si cambiaste recordsize a 16K ayer y todavía ves bloques de 128K, ese archivo no se reescribió.
Decisión: Si los archivos calientes aún usan tamaños antiguos, necesitas un enfoque de reescritura dirigido (o aceptar que no vale la pena reescribir).
Task 10: Identificar snapshots que fijan datos antes de planificar reescrituras
cr0x@server:~$ zfs list -t snapshot -o name,used,refer,creation -s creation tank/app | tail -n 5
tank/app@snap-2025-12-01 85.2G 120G Mon Dec 1 03:00 2025
tank/app@snap-2025-12-08 91.7G 120G Mon Dec 8 03:00 2025
tank/app@snap-2025-12-15 96.4G 120G Mon Dec 15 03:00 2025
tank/app@snap-2025-12-22 101.9G 120G Mon Dec 22 03:00 2025
tank/app@snap-2025-12-26 104.1G 120G Thu Dec 26 03:00 2025
Significado: El “used” de los snapshots está creciendo. Reescribir duplicará bloques y los snapshots conservarán los antiguos, lo que puede duplicar el espacio.
Decisión: Si planeas reescribir/migrar, coordina cambios en retención de snapshots o asegúrate de tener margen libre. Sin margen, no hay migración; habrá pánico después.
Task 11: Estimar el espacio disponible real del dataset (no “df dice que estamos bien”)
cr0x@server:~$ zfs list -o name,used,avail,refer,logicalused,logicalavail tank/app
NAME USED AVAIL REFER LOGICALUSED LOGICALAVAIL
tank/app 2.1T 3.4T 1.9T 2.8T 4.5T
Significado: El usado físico vs usado lógico puede divergir por compresión y snapshots. Los cálculos de margen deben considerar el comportamiento de snapshots durante la reescritura.
Decisión: Si avail está apretado, evita cualquier enfoque que duplique datos (rsync dentro del mismo dataset, copia de archivos que genere churn, reescritura ingenua).
Task 12: Ver si los bloques pequeños especiales están en juego
cr0x@server:~$ zfs get -o name,property,value special_small_blocks tank
NAME PROPERTY VALUE SOURCE
tank special_small_blocks 0 default
Significado: special_small_blocks=0 significa que solo la metadata va a special vdevs (si existen). Si esto está establecido (por ejemplo, 16K), entonces bloques pequeños de archivos pueden ir a dispositivos especiales.
Decisión: Si special_small_blocks está habilitado y el special vdev está caliente, considera aumentar recordsize o ajustar special_small_blocks para evitar empujar demasiados datos a special.
Task 13: Confirmar qué cambió cuando pusiste recordsize (la fuente importa)
cr0x@server:~$ zfs get -r -o name,property,value,source recordsize tank | head
NAME PROPERTY VALUE SOURCE
tank recordsize 128K default
tank/app recordsize 16K local
tank/app/logs recordsize 16K inherited from tank/app
Significado: Cambiaste tank/app y el hijo lo heredó. Eso puede estar bien—o ser daño colateral.
Decisión: Si los logs son de solo anexado y los forzaste a 16K, probablemente reduciste throughput y aumentaste overhead de metadata sin beneficio.
Task 14: Hacer una prueba controlada de “reescribir un subconjunto” con un directorio sacrificial
cr0x@server:~$ zfs create -o recordsize=16K tank/app_test
cr0x@server:~$ rsync -aHAX --info=progress2 /tank/app/hotset/ /tank/app_test/hotset/
sending incremental file list
3.42G 12% 95.11MB/s 0:00:32
28.40G 100% 102.88MB/s 0:04:38 (xfr#231, to-chk=0/232)
Significado: Copiaste el conjunto caliente a un dataset con el recordsize deseado. Ahora los bloques de datos de esos archivos se crearán bajo la nueva política.
Decisión: Haz benchmark de la aplicación contra este dataset de prueba. Si mejora, tienes evidencia. Si no, deja de culpar a recordsize.
Broma #1: Cambiar recordsize y esperar que los datos viejos cambien es como cambiar la marca de café de la oficina y esperar que desaparezcan los incidentes del trimestre pasado.
Estrategias de migración que no reescriben todo
La promesa principal—“cambiar estrategia sin reescribir todo”—solo es parcialmente posible, porque existe la física y la realidad en disco.
Puedes cambiar la política inmediatamente. Puedes evitar reescribir datos fríos. Puedes dirigir reescrituras. También puedes decidir no reescribir y aún así ganar.
Strategy A: Aceptar que solo importan las escrituras nuevas (y hacer que eso baste)
Esta es la estrategia más fácil y subestimada. Muchos datasets son “mayormente append” o “mayormente churn” incluso si parecen estáticos.
Si el conjunto de trabajo caliente se sobreescribe naturalmente, entonces un cambio de política te migrará con el tiempo.
Dónde funciona esto:
- Datasets de logs donde los archivos rotan y los logs antiguos caducan.
- Artefactos temporales de compilación y caches de CI.
- Object stores donde los objetos se reemplazan (no se modifican en sitio) y las políticas de ciclo de vida expiran objetos viejos.
Qué haces:
- Establece el nuevo recordsize.
- Asegura que las escrituras nuevas caigan en el dataset correcto (separar mountpoints si hace falta).
- Observa la distribución de tamaños de bloque en archivos recién escritos durante días/semanas.
Strategy B: Separar datasets por carga de trabajo, no por organigrama
La mayoría de los fallos por recordsize ocurren porque alguien intentó que un dataset sirviera dos amos: IO aleatorio pequeño y throughput secuencial grande.
No lo hagas. Usa múltiples datasets con propiedades distintas y mueve datos según cómo se usan.
Divisiones prácticas que funcionan:
- db/ (aleatorio pequeño): recordsize 16K–32K suele tener sentido para patrones OLTP, pero mide antes.
- wal-or-redo/ (append secuencial, sync): normalmente quiere registros más grandes y atención cuidadosa a sync/SLOG.
- backups/ (secuencial grande): recordsize 1M puede ser excelente.
- home/ o shared/ (mixto): mantiene el 128K por defecto salvo que tengas pruebas.
Separar datasets te da: snapshots separados, cuotas separadas, reservas separadas y—crítico—afinación separada.
Es como los profesionales administran ZFS en producción.
Strategy C: Reescritura dirigida solo de datos calientes
Si el dolor de rendimiento está concentrado en un subconjunto caliente (archivos de base de datos, imágenes de VM, un directorio de índices),
reescribe solo ese subconjunto en un nuevo dataset con el recordsize correcto. Luego cambia la ruta de la aplicación.
Técnicas:
- Crea un nuevo dataset con el recordsize deseado.
- Copiar el árbol de directorios caliente (rsync o export/import a nivel de aplicación).
- Cambiar mountpoints o límites con symlinks (prefiere mountpoints; los symlinks confunden a la gente a las 3am).
- Mantén los datos antiguos disponibles para rollback, pero no los dejes montados donde la app los pueda usar por accidente.
Para bases de datos, las herramientas a nivel de aplicación (dump/restore, restauración física, relocación de tablespaces) suelen producir una reescritura más limpia que copiar a nivel de archivo.
También permiten que la base de datos reempaquete y reduzca fragmentación interna. El rsync a nivel de archivos es toscamente efectivo; a veces eso está bien.
Strategy D: Reescribir in situ copiando archivo por archivo (usar con precaución)
El truco clásico: copiar un archivo sobre sí mismo vía un nombre temporal para que ZFS asigne nuevos bloques bajo el nuevo recordsize.
Esto puede funcionar para un conjunto acotado de archivos cuando puedes tolerar espacio e IO extra.
Las partes feas:
- Los snapshots mantienen los bloques antiguos. El uso de espacio puede explotar.
- Puedes desencadenar una amplificación enorme de escrituras y fragmentación.
- Es difícil hacerlo de forma segura a escala sin un plan de orquestación.
Si lo haces, hazlo en una ventana de mantenimiento dedicada, en una lista conocida de archivos, con monitorización y la política de snapshots ajustada temporalmente.
Strategy E: ZFS send/receive como motor de migración
ZFS send/receive es fantástico para mover datasets entre pools o reorganizar árboles de datasets. No es un reescritor mágico de tamaños de bloque por sí mismo.
Un send/receive reproduce el contenido del dataset y normalmente preserva la estructura de bloques en disco como parte del stream, lo que significa que no “re-recordsizea” tus datos existentes.
Para qué send/receive sí es excelente en este contexto:
- Mover datos a un nuevo pool con diferente layout de vdev mejor adaptado a tu patrón de IO (mirrors vs RAIDZ).
- Reorganizar datasets para que las futuras escrituras sigan políticas correctas.
- Proveer un punto de rollback limpio: si el plan de reescritura falla, puedes revertir re-montando el dataset recibido.
Strategy F: No migrar recordsize; migrar la carga de trabajo
A veces el cuello de botella no es recordsize. Es:
- Una aplicación single-thread que hace escrituras síncronas pequeñas.
- Una base de datos configurada con un scheduler de IO que es hostil a SSDs.
- Un invitado VM emitiendo escrituras aleatorias 4K en un pool RAIDZ sin SLOG y con snapshots pesados.
Si puedes cambiar la carga (hacer escrituras por lotes, cambiar page size de la DB, ajustar checkpointing, mover WAL a un dataset separado),
puedes mantener recordsize estable y aún así obtener una mejora sin reescrituras riesgosas.
Tres micro-historias corporativas desde el campo
Micro-historia 1: El incidente causado por una suposición equivocada
Una empresa mediana ejecutaba una plataforma analítica interna en ZFS. El equipo de almacenamiento notó latencia en queries y vio una ráfaga de posts:
“Las bases de datos prefieren recordsize más pequeño.” Alguien cambió el recordsize del dataset de 128K a 16K en medio de un ciclo de releases.
Nada mejoró. Dos días después, empeoró. El equipo concluyó que el “truco de 16K” no funcionó y lo revirtió.
Ese rollback tampoco hizo nada. Ahora tenían dos cambios y cero causalidad.
El problema real era un conjunto de escaneos secuenciales grandes que chocaban con un job nocturno que generaba archivos parquet grandes y luego los releía inmediatamente.
Los archivos calientes eran mayormente recién generados y en streaming; los 16K aumentaron el overhead de metadata y redujeron la eficiencia de read-ahead.
El pico de latencia se correlacionó con ese job, no con IO aleatorio de la base de datos.
La suposición equivocada fue pensar “base de datos = IO aleatorio”, pero la carga no era OLTP; era analítica con fuertes lecturas secuenciales.
La solución fue aburrida: mantener recordsize en 128K (o mayor para el dataset de salida parquet), aislar las salidas del job en su propio dataset,
y programar los jobs competidores para que no saturaran las mismas colas de vdev.
El ítem de acción post-incidente que quedó: cualquier cambio de recordsize requerirá dos mediciones—distribución de tamaño de IO de la aplicación y ops/throughput a nivel ZFS—antes y después.
Sin medición, no hay cambio.
Micro-historia 2: La optimización que salió mal
Otra organización tenía un gran repositorio de backups en ZFS y quería replicación más rápida. Alguien puso recordsize a 1M en el dataset de backups.
La replicación mejoró. Todos celebraron y se fueron temprano, que es como se invita a problemas.
Un mes después, las restauraciones de archivos pequeños se volvieron notablemente más lentas. No catastrófico, solo molesto—hasta que un incidente obligó a restaurar millones de archivos pequeños de configuración
y manifiestos de paquetes. Se incumplió el SLA de restauración y la dirección empezó a preocuparse.
Lo que salió mal no fue que 1M sea “malo”. Fue que el dataset no era solo backups; también contenía un árbol gigante de archivos pequeños que se accedían frecuentemente durante las restauraciones.
Con registros enormes, leer un archivo pequeño a menudo arrastraba más datos de los necesarios y churneaba el ARC. El sistema hacía más trabajo para el mismo resultado.
La resolución fue dividir el repositorio: un dataset optimizado para streams secuenciales grandes (1M) y otro para catálogos e índices de archivos pequeños (128K o menor).
La replicación siguió rápida y la ruta de restauración dejó de penalizar accesos a archivos pequeños.
La lección operativa: “Nuestro dataset de backups” no es una descripción de carga. Es una etiqueta contable. Recordsize solo respeta cargas de trabajo, no etiquetas.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día
Una empresa regulada operaba servicios al cliente en ZFS con control de cambios estricto. Querían migrar un dataset con imágenes de VM
de un pool legado a uno nuevo con hardware más rápido. También querían “arreglar recordsize mientras tanto”, porque siempre aparece esa tentación.
El equipo hizo algo poco glamoroso: construyó un dataset de staging y ejecutó una carga canaria durante una semana.
Capturaron métricas de zpool iostat, tamaños de IO de los invitados, y latencia p99 de la aplicación. También documentaron el procedimiento de rollback
y lo probaron dos veces.
Durante el canary, aprendieron que recordsize ni siquiera estaba en juego porque eran discos respaldados por zvols. El control correcto era volblocksize,
pero cambiarlo requería recrear zvols. Ese es el tipo de detalle que convierte un “cambio simple” en una “migración planificada.”
Migraron creando nuevos zvols con el volblocksize correcto, haciendo copias a nivel de almacenamiento y cambiando el hipervisor a los volúmenes nuevos.
Aburrido, cuidadoso e indiscutiblemente correcto. También evitaron una caída causada por alguien que intentó afinar recordsize en el tipo de objeto equivocado.
La semana de canary se sintió lenta. Los salvó de una semana mucho más lenta: la de reconstruir sistemas bajo presión.
Errores comunes: síntomas → causa raíz → solución
Aparecen en cronologías reales de incidentes. Los síntomas son familiares. Las causas raíz suelen evitarse. Las soluciones son específicas.
1) “Cambiamos recordsize, pero el rendimiento no cambió en nada.”
- Síntomas: Sin mejora tras el cambio; benchmarks idénticos; zdb muestra tamaños de bloque antiguos.
- Causa raíz: Los datos existentes no se reescribieron; recordsize solo afecta bloques escritos después del cambio.
- Solución: Reescritura dirigida de datos calientes (copiar a un nuevo dataset) o aceptar migración gradual por churn. Verifica con
zdben archivos calientes.
2) “Las escrituras aleatorias pequeñas son lentas; bajamos recordsize y sigue lento.”
- Síntomas: Alta latencia, mucha ops, bajo ancho de banda; pool en RAIDZ; sobreescrituras frecuentes; quizá snapshots pesados.
- Causa raíz: Sobrecarga de read-modify-write por paridad; escrituras sync pueden forzar comportamiento ZIL; snapshots aumentan fragmentación.
- Solución: Considera mirrors para datasets con muchas escrituras aleatorias; aisla componentes sync-heavy; evalúa SLOG; reduce churn de snapshots en datos calientes.
3) “El throughput cayó después de poner recordsize en 16K.”
- Síntomas: Jobs secuenciales más lentos; más CPU en kernel; más IO de metadata; menor eficiencia de prefetch.
- Causa raíz: El dataset es secuencial/streaming, pero se redujo recordsize, aumentando el recuento de bloques y overhead.
- Solución: Restaurar recordsize más grande (128K–1M) para datasets de streaming; dividir cargas en datasets separados.
4) “Reescribimos datos y nos quedamos sin espacio.”
- Síntomas: El pool se llena durante la migración; los borrados no liberan espacio; todo empeora rápidamente.
- Causa raíz: Snapshots fijan bloques antiguos; la reescritura duplica datos; margen insuficiente.
- Solución: Ajustar retención de snapshots durante la migración; clonar a un nuevo dataset y hacer el cutover; añadir capacidad temporal; planificar margen antes de reescribir.
5) “El special vdev se saturó después de ajustar recordsize.”
- Síntomas: El dispositivo special muestra %util alto; picos en latencia de metadata; latencia general del pool aumenta.
- Causa raíz:
special_small_blockshace que más bloques de datos aterrizen en special vdev; la distribución recordsize/bloques pequeños cambió. - Solución: Re-evalúa el umbral de
special_small_blocks; mueve datos con muchos bloques pequeños fuera de special; asegura que el special vdev tenga suficiente rendimiento/durabilidad.
6) “Afinamos recordsize para VMs y nada cambió.”
- Síntomas: Rendimiento de VMs sin cambios; cambiaste recordsize en un dataset que almacena zvols o archivos sparse con patrones de IO distintos.
- Causa raíz: Las VMs están en zvols donde importa volblocksize; o el hipervisor usa patrones de IO directos no alineados con tu suposición.
- Solución: Afina volblocksize al crear zvol; usa datasets separados para imágenes de VM si son basadas en archivos; mide tamaños de IO del invitado.
Broma #2: La forma más rápida de descubrir que no tienes suficiente margen en el pool es iniciar una reescritura “rápida” a las 16:55 del viernes.
Listas de verificación / plan paso a paso
Este es el plan que querría tener delante durante una ventana de cambio: lo suficientemente corto para usar, lo bastante estricto para evitar heridas auto-infligidas.
Checklist 1: Decidir si recordsize es realmente la palanca
- Mide la distribución de tamaños de IO (
iostat -x, métricas de la aplicación, métricas del hipervisor). - Mide ops ZFS vs ancho de banda (
zpool iostat -v). - Identifica si estás bound por sync (busca comportamiento fsync-heavy; verifica presencia/rendimiento de SLOG).
- Confirma tipo de dataset (filesystem vs zvol).
- Confirma snapshots y margen para cualquier plan de reescritura.
Checklist 2: Elegir una estrategia de migración
- Cambio de política (sin reescritura): Úsalo cuando los datos churnean naturalmente o los datos calientes son mayormente nuevas escrituras.
- Separar datasets: Úsalo cuando tienes cargas mixtas en un mismo dataset.
- Reescritura dirigida: Úsalo cuando un directorio/conjunto de archivos específico causa el problema.
- Migración de carga: Úsalo cuando la aplicación puede cambiar (batching, tamaños de página, separación de WAL).
- Migración de layout del pool: Úsalo cuando la sobrecarga de paridad es el problema y necesitas mirrors o hardware distinto.
Checklist 3: Ejecutar un cutover seguro de reescritura dirigida
- Crea un nuevo dataset con recordsize y propiedades deseadas (compresión, atime, xattr, etc.).
- Haz una copia canaria pequeña y valida tamaños de bloque en algunos archivos calientes (
zdb -ddddd). - Haz benchmark de la aplicación contra la nueva ubicación (mismo conjunto de queries, misma carga de VM, mismo job de backup).
- Planifica la política de snapshots: reduce retención durante la migración o asegura margen suficiente.
- Rsync/copia el subconjunto caliente; verifica checksums o consistencia a nivel de aplicación.
- Corta mediante switch de mountpoint; deja el dataset viejo en solo-lectura para rollback.
- Monitorea p95/p99 de latencia, cola del pool, special vdev, SLOG si existe.
- Tras la estabilización, retira los datos viejos y restaura la retención de snapshots.
Checklist 4: Validación post-cambio (no omitas lo aburrido)
- Confirma fuentes de propiedades: recordsize establecido donde se pretendía, heredado donde se pretendía.
- Confirma que los archivos calientes se escriben bajo la nueva política.
- Confirma que no hubo datasets inesperados que heredaron el cambio.
- Compara métricas antes/después: ops, ancho de banda, latencia, CPU.
- Documenta el layout final de datasets y por qué existe cada recordsize.
Preguntas frecuentes
1) Si cambio recordsize, ¿mis archivos existentes se reestructurarán automáticamente?
No. Los bloques existentes permanecen tal cual. Solo los bloques recién escritos siguen el nuevo tamaño máximo.
Si necesitas que los archivos calientes cambien, debes reescribirlos (directa o indirectamente) para que ZFS asigne nuevos bloques.
2) ¿Qué recordsize debería usar para bases de datos?
Depende de patrones de acceso y del tamaño de página de la base de datos. Muchas cargas OLTP se benefician de recordsize más pequeño (a menudo 16K–32K),
pero los escaneos analíticos prefieren registros más grandes. Mide tamaños de IO y latencia primero; luego prueba en un dataset subset.
3) ¿1M de recordsize siempre es lo mejor para backups?
A menudo, pero no siempre. Si el dataset es verdaderamente flujos secuenciales grandes, 1M puede mejorar throughput y compresión.
Si también almacena catálogos o índices pequeños y frecuentemente accedidos, sepáralos en otro dataset.
4) ¿ZFS send/receive puede reescribir datos en un nuevo recordsize?
No por sí mismo. Send/receive preserva la estructura de bloques existente en el stream.
Usa send/receive para mover datasets entre pools o reorganizar; usa reescrituras dirigidas o reconstrucciones a nivel de aplicación para cambiar el tamaño de bloque en disco.
5) ¿Por qué bajar recordsize a veces hace que todo vaya más lento?
Los registros más pequeños aumentan el recuento de bloques, el overhead de metadata y pueden reducir la eficiencia de read-ahead para cargas secuenciales.
Puedes intercambiar eficiencia de actualización aleatoria por throughput secuencial. Si tu carga es mayormente streaming, recordsize pequeño es autosabotaje.
6) ¿Cómo afectan los snapshots la migración de recordsize?
Los snapshots mantienen los bloques antiguos. Si reescribes datos para forzar nuevos tamaños de bloque, vas a asignar nuevos bloques mientras los snapshots retienen los antiguos.
El uso de espacio puede dispararse. Planifica margen y retención de snapshots antes de reescribir.
7) ¿Debo afinar recordsize para discos de VM?
Si tus discos de VM son zvols, recordsize es irrelevante; volblocksize importa y suele elegirse al crear el zvol.
Si los discos de VM son archivos en un dataset filesystem, recordsize puede importar—pero los patrones de IO del invitado siguen siendo los que mandan. Mídelos.
8) ¿Cómo puedo saber si mis archivos calientes usan el nuevo recordsize?
Usa zdb -ddddd en archivos representativos y mira los tamaños de bloques de datos (por ejemplo, 16K vs 128K).
Además, copia un conjunto de prueba pequeño a un nuevo dataset con el recordsize deseado y compara rendimiento.
9) ¿La compresión cambia el “recordsize correcto”?
Puede. Los registros más grandes a menudo comprimen mejor, lo que puede reducir IO físico.
Pero si frecuentemente lees porciones pequeñas, puedes pagar costes de descompresión y amplificación de lectura. Prueba con patrones reales de acceso.
10) Si recordsize es solo un máximo, ¿por qué la gente lo obsesiona?
Porque influye fuertemente en cómo ZFS dispone archivos grandes, y los archivos grandes son donde se esconden problemas de rendimiento:
bases de datos, imágenes de VM, grandes logs, backups, índices. La obsesión está justificada; el consejo simplista no lo está.
Pasos prácticos siguientes
Si recuerdas solo tres cosas: recordsize es un techo, no reescribe datos antiguos, y la vía más rápida a una migración segura es separar datasets más reescrituras dirigidas.
Todo lo demás es detalle de implementación—y el detalle de implementación es donde nacen los incidentes.
- Mide primero: captura distribución de tamaños de IO, ops/ancho de banda del pool y comportamiento sync.
- Define el alcance del cambio: confirma tipos de dataset y herencia para no “optimizar” tus logs hacia una regresión.
- Escoge la migración menos arriesgada: solo política para datos con churn, reescrituras dirigidas para subconjuntos calientes, separación de datasets para cargas mixtas.
- Valida con evidencia:
zdbpara tamaños de bloque, latencia p99 de la aplicación para impacto en usuarios, yzpool iostatpara comportamiento del pool. - Anota qué hiciste y por qué: el tú del futuro estará cansado, y la gente cansada merece buena documentación.