Todo está bien hasta que deja de estarlo: arranca un trabajo de backup, una base de datos se atasca y de repente los hosts de VM se sienten “lentos” de una manera que nadie puede cuantificar.
Miras gráficos, discutes sobre la “latencia de almacenamiento” y alguien sugiere reiniciar cosas “para limpiarlo”.
La solución rara vez es heroica. Es una estructura aburrida: un dataset ZFS por carga de trabajo, con propiedades explícitas, reglas de snapshots explícitas y límites explícitos.
Así evitas que el pool de almacenamiento se convierta en una cocina compartida donde todos cocinan curry a la vez y nadie encuentra una sartén limpia.
El truco: los datasets son límites de gestión
ZFS no es solo un sistema de archivos. Es un motor de políticas soldado a un asignador, con checksumming y copy-on-write como pegamento.
Ese motor de políticas se controla mayoritariamente mediante propiedades de dataset. Y esas propiedades solo importan si usas datasets como límites.
Una estrategia de “dataset por carga de trabajo” significa que dejas de tratar tu pool como un gran árbol de directorios donde todo hereda lo que al último administrador le dio la gana.
En su lugar, creas datasets que mapean cargas reales: una base de datos, un almacén de imágenes de VM, una caché de objetos, un repositorio de artefactos de compilación, directorios home,
capas de contenedores, una zona de recepción de backups. Cada uno recibe ajustes deliberados.
El punto no es “más datasets porque ZFS puede”. El punto es el control operacional:
- Aislamiento de rendimiento: recordsize, logbias, atime, comportamiento de sync y special_small_blocks se pueden ajustar por carga.
- Aislamiento de seguridad: cadencia de snapshots, retención, holds y replicación pueden ser por dataset.
- Disciplina de capacidad: cuotas/reservas evitan que una carga se coma tu pool llamándolo “temporal”.
- Control del radio de daño: puedes revertir o enviar/recibir una carga sin tocar a los vecinos.
- Diagnostibilidad: zfs get, zfs list y estadísticas por dataset se vuelven significativas en lugar de una sopa.
Si recuerdas una cosa: tu pool ZFS es el presupuesto de hardware; los datasets son tus contratos.
Sin contratos, recibes facturas sorpresa.
Un chiste corto, por servicio: Si tu almacenamiento es “un gran dataset”, no es arquitectura—es una emoción.
Hechos interesantes y contexto histórico (porque las cicatrices tienen historia)
- ZFS se originó en Sun Microsystems a principios y mediados de los 2000, diseñado para reemplazar gestores de volúmenes y sistemas de archivos tradicionales con un sistema integrado.
- El modelo copy-on-write fue una respuesta directa a la corrupción silenciosa y a la clase de problemas “write hole” que acosaban a las pilas RAID + sistema de archivos anteriores.
- Los snapshots de ZFS fueron diseñados para ser baratos porque son solo referencias a bloques existentes—hasta que los mantienes para siempre y te preguntas por qué los borrados no liberan espacio.
- La herencia de propiedades de dataset es intencional: permite árboles de políticas, pero también habilita herencias accidentales de políticas—como una base de datos de producción heredando un horario de snapshots “de prueba”.
- La compresión se volvió una práctica común en ZFS porque a menudo mejora el rendimiento al reducir I/O, especialmente en discos giratorios y arreglos saturados.
- Recordsize existe porque “un tamaño de bloque sirve para todo” falla: archivos secuenciales grandes y I/O aleatorio pequeño necesitan distintos compromisos.
- Los ZVOLs son dispositivos de bloque respaldados por ZFS; se comportan diferente de los datasets y traen sus propios ajustes como volblocksize.
- El ARC no es “solo caché”; es un consumidor de memoria con comportamiento de expulsión que puede hacer o deshacer el rendimiento, especialmente bajo cargas mixtas.
- OpenZFS se convirtió en la continuación multiplataforma después de la era de Sun, y las funciones modernas (como special vdevs y dRAID) son producto de años de feedback operacional.
Todo ese historial apunta a la misma verdad operacional: ZFS quiere que expreses intención. Los datasets son cómo lo haces.
Mapa de cargas: qué debe ir en su propio dataset
“Por carga de trabajo” no es “por directorio”. Es “por comportamiento”. Divides cuando una carga tiene necesidades distintas de latencia, rendimiento, seguridad o retención.
Mantienes junto lo que comparte ciclo de vida y política.
Buenas fronteras de dataset (pragmáticas, no académicas)
- Bases de datos: directorio de datos de Postgres/MySQL, WAL/binlog y backups a menudo merecen datasets separados.
- Almacenamiento de VM: un dataset para imágenes de VM, opcionalmente por clúster o por tenant. Si usas ZVOLs, trátalos también como cargas.
- Contenedores: capas de imagen vs volúmenes escribibles vs logs. Tienen patrones de escritura muy distintos.
- Caches de build/artefactos: alto churn, bajo valor, muchas eliminaciones—perfecto para reglas de snapshot/retención distintas.
- Directorios home: cuotas, retención distinta y restauraciones por “ups, lo borré”.
- Zonas de recepción de backups: recepciones, retención larga y “no hacer snapshots cada 5 minutos por accidente”.
- Logs: a menudo se comprimen bien, pero no siempre necesitan snapshots; además pueden ser enormes y con picos.
Dónde la gente sobre-separa
No crees un dataset para cada subdirectorio de una aplicación porque te emocionaste con los ajustes.
Si tu horario de snapshots es idéntico, la retención es idéntica y las características de rendimiento son idénticas, mantenlo junto.
Gestionar 400 datasets sin estándar de nombres es cómo construyes un museo de intenciones olvidadas.
Nomenclatura: hazla aburrida y buscable
Usa una jerarquía predecible. Ejemplo:
tank/prod/db/postgres, tank/prod/db/postgres-wal, tank/prod/vm, tank/prod/containers,
tank/shared/homes, tank/backup/recv.
Pon el entorno y la función al principio. Harás grep a las 02:00. No obligues al Futuro Tú a interpretar poesía.
Propiedades que importan (y lo que realmente hacen)
recordsize: la palanca silenciosa de rendimiento
recordsize controla el tamaño máximo de bloque para archivos en un dataset. Más grande es bueno para lecturas/escrituras secuenciales grandes (media, backups).
Más pequeño puede reducir la amplificación read-modify-write para patrones I/O aleatorio (bases de datos).
Para muchas bases de datos en datasets (no ZVOLs), recordsize=16K o recordsize=8K es un punto de partida común.
Para imágenes de VM almacenadas como archivos, recordsize=128K suele funcionar bien. Para flujos de backup, recordsize=1M puede ser apropiado.
Pero no hagas esto en modo cargo-cult; mide.
volblocksize: para ZVOLs, no para archivos
Los ZVOLs usan volblocksize. Se establece en el momento de creación y cambiarlo después no es trivial.
Ajústalo al tamaño de bloque del huésped/sistema de archivos y al I/O esperado: por ejemplo, 8K o 16K para I/O aleatorio tipo BD; más grande para secuencial.
compression: característica de rendimiento disfrazada de eficiencia de almacenamiento
Usa compression=zstd para la mayoría de los datasets a menos que tengas una razón clara para no hacerlo.
Normalmente es más rápido que tus discos y reduce las escrituras. También reduce el drama de “¿por qué mi pool está lleno?”.
atime: escrituras de metadatos que probablemente no quieres
atime=off es un movimiento estándar para la mayoría de cargas de servidor. Si realmente necesitas actualizaciones de tiempo de acceso, haz ese dataset explícito y pequeño.
sync y logbias: donde la honestidad se encuentra con la latencia
sync=standard es la opción por defecto y la más segura. sync=disabled puede hacer que los benchmarks se vean bien y la producción parezca una necropsia.
logbias=latency vs throughput influye en la intención para escrituras síncronas (y si el SLOG ayuda).
Si cambias el comportamiento de sync, trátalo como cambiar tu RPO: escríbelo, consigue aprobación y asume que la realidad te auditará.
primarycache / secondarycache: elige qué cachear
Para datasets que contaminan el ARC (como grandes lecturas secuenciales de backups), considera primarycache=metadata.
No se trata de ahorrar RAM; se trata de mantener útil el ARC para cargas sensibles a la latencia.
special_small_blocks y special vdevs: metadatos rápidos, I/O pequeño rápido
Si tienes un special vdev (normalmente SSDs) para metadatos/bloques pequeños, special_small_blocks puede mejorar dramáticamente cargas de archivos pequeños.
También puede causar un arrepentimiento dramático si tu special vdev está subdimensionado. Esta es una de esas funciones donde “planifica capacidad como si fuera producción, porque lo es”.
quotas, reservations, refreservation: capacidad como política
quota limita el crecimiento. reservation garantiza espacio. refreservation puede reservar espacio igual a los datos referenciados de un dataset,
usado a menudo para zvols. Estos son tus barandales contra el impuesto del vecino ruidoso del almacenamiento.
snapshots: tu palanca de rollback, tu unidad de replicación, tu trampa espacio-temporal
Los snapshots son baratos hasta que no lo son. Un dataset con alto churn y retención larga de snapshots retendrá bloques eliminados e inflará el espacio usado.
Un dataset con snapshots frecuentes y churn rápido puede llenar un pool mientras todos juran que nada cambió.
Una cita, porque sigue siendo cierta
La esperanza no es una estrategia.
— General H. Norman Schwarzkopf
Tareas prácticas: comandos + qué significa la salida + la decisión que tomas
Estos no son comandos “de juguete”. Son los que ejecutas cuando estás cansado, la producción está caliente y tu pool de almacenamiento recibe la culpa de todo.
Cada tarea incluye: comando, salida de ejemplo, interpretación y una decisión.
Tarea 1: Listar datasets y ver quién está usando realmente espacio
cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint -r tank/prod
NAME USED AVAIL REFER MOUNTPOINT
tank/prod 3.21T 5.87T 192K /tank/prod
tank/prod/db 1.44T 5.87T 192K /tank/prod/db
tank/prod/db/postgres 812G 5.87T 812G /var/lib/postgresql
tank/prod/db/postgres-wal 221G 5.87T 221G /var/lib/postgresql-wal
tank/prod/vm 1.55T 5.87T 1.55T /tank/prod/vm
tank/prod/containers 218G 5.87T 218G /var/lib/containers
Qué significa: USED incluye snapshots/descendientes; REFER es el dato vivo en ese dataset.
Decisión: Si USED es mucho mayor que REFER, los snapshots están acaparando espacio. Investiga la retención de snapshots para ese dataset.
Tarea 2: Encontrar la hinchazón de snapshots por dataset
cr0x@server:~$ zfs list -t snapshot -o name,used,refer -s used -r tank/prod/db/postgres | tail -n 5
tank/prod/db/postgres@hourly-2025-12-25-00 18.4G 812G
tank/prod/db/postgres@hourly-2025-12-24-23 18.2G 812G
tank/prod/db/postgres@hourly-2025-12-24-22 17.9G 812G
tank/prod/db/postgres@hourly-2025-12-24-21 17.6G 812G
tank/prod/db/postgres@hourly-2025-12-24-20 17.4G 812G
Qué significa: El USED de snapshot aquí son los bloques únicos retenidos por ese snapshot.
Decisión: Si los snapshots son enormes y frecuentes, acorta la retención o haz snapshots con menos frecuencia en datasets de alto churn (WAL, caches, artefactos de compilación).
Tarea 3: Comprobar la herencia de propiedades para detectar valores por defecto accidentales
cr0x@server:~$ zfs get -r -o name,property,value,source recordsize,compression,atime,sync,logbias tank/prod/db/postgres
NAME PROPERTY VALUE SOURCE
tank/prod/db/postgres recordsize 128K inherited from tank/prod
tank/prod/db/postgres compression zstd inherited from tank/prod
tank/prod/db/postgres atime on default
tank/prod/db/postgres sync standard default
tank/prod/db/postgres logbias latency local
Qué significa: Esta base de datos está heredando recordsize=128K y tiene atime=on.
Decisión: Establece recordsize explícitamente para datasets de BD y desactiva atime a menos que lo necesites.
Tarea 4: Corregir recordsize y atime para un dataset de base de datos
cr0x@server:~$ sudo zfs set recordsize=16K atime=off tank/prod/db/postgres
Qué significa: Las escrituras futuras usarán registros de 16K; los bloques existentes no se reescribirán hasta que los datos cambien.
Decisión: Aplica durante una ventana de mantenimiento si planeas reescribir grandes porciones; de lo contrario acepta la mejora gradual.
Tarea 5: Comprobar compressratio para ver si la compresión ayuda
cr0x@server:~$ zfs get -o name,property,value compressratio -r tank/prod | head
NAME PROPERTY VALUE
tank/prod compressratio 1.42x
tank/prod/db compressratio 1.12x
tank/prod/db/postgres compressratio 1.06x
tank/prod/vm compressratio 1.68x
Qué significa: Las imágenes de VM se comprimen bien; la base de datos se comprime poco (común en páginas ya comprimidas o datos encriptados).
Decisión: Mantén la compresión activada salvo que la CPU esté muy limitada. Para la BD, la compresión no ahorrará mucho espacio pero puede reducir ligeramente las escrituras.
Tarea 6: Poner un límite duro en una carga “temporal”
cr0x@server:~$ sudo zfs set quota=500G tank/prod/containers
cr0x@server:~$ zfs get -o name,property,value quota tank/prod/containers
NAME PROPERTY VALUE
tank/prod/containers quota 500G
Qué significa: El dataset de containers no puede superar 500G.
Decisión: Usa cuotas para caches, artefactos de build y colas de logs. Si alcanza la cuota, obtienes una falla controlada en lugar de una caída del pool completo.
Tarea 7: Reservar espacio para que la base de datos pueda seguir respirando
cr0x@server:~$ sudo zfs set reservation=1T tank/prod/db
cr0x@server:~$ zfs get -o name,property,value reservation tank/prod/db
NAME PROPERTY VALUE
tank/prod/db reservation 1T
Qué significa: Estás garantizando espacio para datasets de BD bajo tank/prod/db.
Decisión: Usa reservas cuando el coste empresarial de fallos de escritura en BD sea mayor que el coste de “malgastar” capacidad reservada.
Tarea 8: Identificar si el pool sufre presión por fragmentación
cr0x@server:~$ zpool list -o name,size,alloc,free,frag,capacity,health tank
NAME SIZE ALLOC FREE FRAG CAPACITY HEALTH
tank 9.08T 7.24T 1.84T 63% 79% ONLINE
Qué significa: 79% lleno y 63% de fragmentación es una señal de advertencia, no un crimen. ZFS puede funcionar así, pero la latencia suele subir.
Decisión: Planifica capacidad: mantén pools por debajo de ~80% para cargas mixtas aleatorias. Reduce datasets de churn o añade vdevs antes de la fase de “latencia misteriosa”.
Tarea 9: Vigilar la latencia de I/O a nivel de pool
cr0x@server:~$ zpool iostat -v tank 2 3
capacity operations bandwidth
pool alloc free read write read write
---------- ----- ----- ----- ----- ----- -----
tank 7.24T 1.84T 640 1200 78.2M 94.6M
raidz2-0 7.24T 1.84T 640 1200 78.2M 94.6M
sda - - 80 150 9.70M 11.8M
sdb - - 79 151 9.66M 11.7M
sdc - - 81 148 9.74M 11.6M
sdd - - 80 149 9.69M 11.7M
Qué significa: Ves la distribución de carga. Esta vista por sí sola no muestra latencia, pero indica si estás saturando dispositivos.
Decisión: Si las escrituras son consistentemente altas y la carga es muy sincrónica, evalúa SLOG y ajustes de sync por dataset (no globalmente).
Tarea 10: Inspeccionar la contabilidad de espacio lógico por dataset (y detectar trampas de refreservation)
cr0x@server:~$ zfs list -o name,used,refer,logicalused,logicalrefer,compressratio -r tank/prod/vm | head
NAME USED REFER LUSED LREFER RATIO
tank/prod/vm 1.55T 1.55T 2.31T 2.31T 1.68x
Qué significa: El uso lógico es mayor que el uso físico gracias a la compresión (y posiblemente imágenes sparse).
Decisión: Si el uso lógico es enorme, asegura que tu monitorización alerte sobre la capacidad física del pool, no solo la capacidad reclamada por los huéspedes.
Tarea 11: Ver qué está golpeando el ARC y si debes limitar la caché para un dataset
cr0x@server:~$ arcstat 1 3
time read miss miss% dmis dm% pmis pm% mmis mm% size c
12:00:01 712 144 20 112 16 32 4 0 0 46.3G 48.0G
12:00:02 690 171 24 139 20 32 5 0 0 46.1G 48.0G
12:00:03 705 162 23 131 19 31 4 0 0 46.0G 48.0G
Qué significa: Una tasa de fallos de 20–24% bajo carga puede ser aceptable o terrible según los objetivos de latencia. Sugiere que el conjunto de trabajo puede no caber.
Decisión: Si trabajos secuenciales grandes están desgastando el ARC, pon primarycache=metadata en esos datasets (p. ej., receive de backups o archivos de medios).
Tarea 12: Cambiar la política de caché para un dataset de backups y proteger cargas sensibles a latencia
cr0x@server:~$ sudo zfs set primarycache=metadata tank/backup/recv
cr0x@server:~$ zfs get -o name,property,value,source primarycache tank/backup/recv
NAME PROPERTY VALUE SOURCE
tank/backup/recv primarycache metadata local
Qué significa: ZFS cacheará metadatos, pero no datos de archivos, para este dataset en ARC.
Decisión: Usa esto para datasets “de streaming” que no se benefician del cache y que dañan activamente al resto.
Tarea 13: Crear un dataset con valores predeterminados adecuados para la carga (el tipo correcto de aburrido)
cr0x@server:~$ sudo zfs create -o compression=zstd -o atime=off -o recordsize=16K tank/prod/db/mysql
cr0x@server:~$ zfs get -o name,property,value,source compression,atime,recordsize tank/prod/db/mysql
NAME PROPERTY VALUE SOURCE
tank/prod/db/mysql compression zstd local
tank/prod/db/mysql atime off local
tank/prod/db/mysql recordsize 16K local
Qué significa: Has hecho explícita la intención al crear, evitando sorpresas de herencia accidental.
Decisión: Estandariza la creación de datasets vía runbook o automatización. Los humanos son geniales; la consistencia es mejor.
Tarea 14: Comprobar la deriva en la política de snapshots (¿estás snapshotteando lo equivocado?)
cr0x@server:~$ zfs get -r -o name,property,value,source com.sun:auto-snapshot tank/prod | head -n 8
NAME PROPERTY VALUE SOURCE
tank/prod com.sun:auto-snapshot true inherited from tank
tank/prod/db com.sun:auto-snapshot true inherited from tank
tank/prod/db/postgres com.sun:auto-snapshot true inherited from tank
tank/prod/db/postgres-wal com.sun:auto-snapshot true inherited from tank
tank/prod/vm com.sun:auto-snapshot true inherited from tank
tank/prod/containers com.sun:auto-snapshot true inherited from tank
tank/prod/logs com.sun:auto-snapshot true inherited from tank
Qué significa: Todo está siendo snapshotteado, incluyendo WAL y logs, que pueden tener alto churn y bajo valor.
Decisión: Desactiva autosnapshots en datasets de alto churn (WAL/logs/caches) o dales una clase de retención distinta.
Tarea 15: Desactivar snapshots en un dataset de alto churn (ejemplo: WAL)
cr0x@server:~$ sudo zfs set com.sun:auto-snapshot=false tank/prod/db/postgres-wal
cr0x@server:~$ zfs get -o name,property,value,source com.sun:auto-snapshot tank/prod/db/postgres-wal
NAME PROPERTY VALUE SOURCE
tank/prod/db/postgres-wal com.sun:auto-snapshot false local
Qué significa: La automatización de snapshots que respete esta propiedad omitirá WAL.
Decisión: Protege WAL mediante backups/archivado nativo de la base de datos, no con snapshots del sistema de archivos cada 15 minutos que retienen churn para siempre.
Tarea 16: Confirmar ashift y diseño de vdevs (porque no puedes ajustar geometría equivocada)
cr0x@server:~$ zdb -C tank | egrep "ashift|vdev_tree|type:|path:"
ashift: 12
type: 'raidz'
type: 'disk'
path: '/dev/disk/by-id/ata-SAMSUNG_SSD_...'
Qué significa: ashift: 12 implica alineación de sectores de 4K, típicamente correcta para discos/SSDs modernos.
Decisión: Si ashift es incorrecto (demasiado pequeño), el rendimiento y la amplificación de escritura pueden dañarse permanentemente. Corregirlo suele significar reconstruir el pool.
Segundo chiste corto, porque te lo has ganado: Lo bueno de los datasets “temporales” es que duran para siempre—como los tatuajes, pero con peores políticas de retención.
Guía de diagnóstico rápido: encuentra el cuello de botella sin una semana de reuniones
Este es el orden de triaje que funciona en la vida real: comienza amplio y luego enfoca. No empieces cambiando recordsize.
No empieces culpando a ZFS. Y absolutamente no empieces por “apagar sync” porque alguien vio un post en un foro de 2014.
Primero: ¿El pool se quedó sin espacio para respirar?
- Comprueba capacidad y fragmentación del pool:
zpool list - Revisa la hinchazón por snapshots:
zfs list -t snapshot - Comprueba si un solo dataset está explotando:
zfs list -r
Interpretación: Los pools que viven cerca del lleno tienden a convertirse en máquinas de latencia.
Alto churn + muchos snapshots + alto nivel de llenado es una receta clásica para incidentes de “se volvió lento”.
Segundo: ¿Es IOPS/latencia, throughput o CPU?
- Mira la saturación y distribución de dispositivos:
zpool iostat -v 1 - Mira el steal/iowait de CPU del sistema:
mpstat -P ALL 1,iostat -x 1 - Comprueba el coste de CPU de la compresión si usas algoritmos pesados:
zfs get compressiony métricas de CPU del sistema
Interpretación: Los cuellos de botella de latencia de escritura aleatoria se sienten como “todo está lento”. Los cuellos de throughput se sienten como “los trabajos grandes tardan”.
Los cuellos de CPU se sienten como “el almacenamiento es lento” porque la pila de almacenamiento espera a la CPU para checksumming, compresión y gestión de metadatos.
Tercero: ¿Qué dataset es el vecino ruidoso?
- Identifica los mayores escritores/lectores por proceso:
iotop -oopidstat -d 1 - Correlaciona con mountpoints/datasets:
zfs list -o name,mountpoint - Confirma propiedades del dataset:
zfs get -o name,property,value,source -r ...
Interpretación: Si un dataset está haciendo escrituras secuenciales con snapshots y caché activada, puede expulsar ARC útil y causar misses para todos.
Si un dataset es muy sincrónico y carece de configuración de intención/log adecuada, puede dominar la latencia.
Cuarto: ¿Estás luchando contra tus propias políticas?
- Ámbito de la automatización de snapshots: revisa
com.sun:auto-snapshoto tus banderas locales de snapshot - Retraso de replicación y receives:
zfs list -t snapshoty busca snapshots antiguos “atascados” para send/receive - Cuotas/reservas:
zfs get quota,reservation,refreservation
Interpretación: Muchos tickets de “ZFS se está comiendo el espacio” son en realidad “los snapshots hacen exactamente lo que pedimos, y lo pedimos mal”.
Tres mini-historias corporativas desde las trincheras de almacenamiento
1) El incidente causado por una suposición equivocada: “los snapshots son gratis”
Una empresa mediana usaba un pool ZFS para una flota mixta: VMs, contenedores y un par de bases de datos. Alguien habilitó una política de autosnapshot
en la raíz del pool. Snapshots horarios para todo. Retención diaria por un mes. Se vendió como “una red de seguridad”.
La suposición equivocada no fue maliciosa; fue optimista. Los snapshots son baratos para crear, así que el equipo asumió que eran baratos para mantener.
Mientras tanto, el dataset de contenedores tenía churn agresivo: pulls de imágenes, eliminaciones de capas, jobs de CI, rotación de logs, la entropía habitual.
Las eliminaciones no liberaban espacio. El pool se llenó lentamente y luego de repente.
El incidente empezó como un síntoma de base de datos: los commits se ralentizaron. Luego el clúster de VM comenzó a experimentar timeouts de huéspedes.
Operaciones miró gráficos y vio la utilización de disco subir y nunca bajar. Alguien intentó borrar imágenes de contenedores antiguas—sin retorno inmediato de espacio.
El on-call terminó en el mismo pozo que muchos conocemos: “¿por qué nada libera espacio?”
La corrección post-incident no fue tuning exótico. Separaron datasets por carga, desactivaron snapshots en caches y datasets de alto churn,
y dieron a cada dataset una política de retención acorde a su valor. La BD recibió snapshots frecuentes con retención corta; la tienda de VM snapshots nocturnos;
contenedores ninguno, con backups manejados en el registro de artefactos.
La lección: los snapshots no son gratis; son coste diferido. Un límite de dataset es donde decides qué coste estás dispuesto a pagar.
2) La optimización que salió mal: “sync=disabled para velocidad”
Otra empresa tenía un problema de rendimiento: su carga de Postgres sufría latencia alta en commits tras una migración a nuevo almacenamiento.
Un ingeniero bienintencionado ejecutó un benchmark, vio números pobres y aplicó el “arreglo” clásico: sync=disabled en el dataset de la BD.
Los gráficos de benchmark mejoraron al instante. La gente celebró. El ticket se cerró con un comentario confiado sobre “sobrecarga de ZFS”.
Semanas después, tuvieron un evento de pérdida de energía no planificado. No un gran incendio de centro de datos—solo el tipo de problema eléctrico que te hace aprender
cuán buen mantenimiento tiene tu UPS. Los sistemas volvieron. Postgres también, pero no limpio. La base de datos necesitó recuperación y mostró signos
de corrupción en una porción de transacciones recientes. El incidente se convirtió en un ejercicio interequipos en backups, archivos WAL y conversaciones incómodas.
El fallo operacional no fue solo el cambio de propiedad; fue la falta de límites de dataset y documentación sobre la intención.
El dataset de BD había heredado otros valores por defecto de un árbol de datasets de propósito general, y nadie rastreaba qué datasets estaban “mintiendo” sobre sync.
Cuando llegó la auditoría de riesgo, el equipo no supo qué buscar.
La corrección fue conservadora: restaurar sync=standard, validar la idoneidad del SLOG (o aceptar la latencia), y separar WAL en su propio dataset
con propiedades deliberadas. También añadieron una comprobación simple de cumplimiento: zfs get -r sync en pools de prod, marcado en monitorización.
La lección: los hacks de rendimiento que cambian la corrección no son tuning; son cambios de política. Usa límites de dataset para hacer esas políticas explícitas,
auditables y raras.
3) La práctica aburrida pero correcta que salvó el día: cuotas + reservas + propiedad clara de datasets
Una compañía que ejecutaba una plataforma interna tenía un pool compartido por múltiples equipos de producto. El equipo de plataforma impuso una regla:
cada equipo tenía un dataset, con una cuota, y las bases de datos de producción tenían reservas. Todos se quejaron, porque todos se quejan cuando se imponen guardarraíles.
En un fin de trimestre, una carga de data science comenzó a volcar datos intermedios en su dataset—perfectamente razonable en aislamiento, catastrófico en agregado.
La carga llegó a su cuota. Los jobs fallaron. El equipo llamó a plataforma con el mensaje habitual “el almacenamiento está roto”.
Pero la producción se mantuvo sana. Las bases de datos siguieron haciendo commits. Las imágenes de VM siguieron moviéndose. Nadie más lo notó.
El on-call del equipo de plataforma no necesitó heroísmos. Tenían un dominio de falla claro: un dataset, una cuota, un propietario.
Trabajaron con el equipo para aumentar la cuota con un plan o mover la carga a otro pool diseñado para datos temporales.
El impacto en la capacidad se discutió antes de imponerse.
La lección: la característica ZFS más efectiva para la cordura multi-tenant no es una caché sofisticada. Es la política como propiedades, aplicada mediante datasets,
con propietarios a los que se puede avisar cuando su dataset se porta mal.
Errores comunes: síntomas → causa raíz → solución
1) Síntoma: “Borramos terabytes pero el pool sigue lleno”
Causa raíz: Los snapshots están reteniendo bloques eliminados; los borrados solo liberan espacio cuando ningún snapshot referencia esos bloques.
Solución: Identifica los usuarios de espacio de snapshots; acorta la retención; excluye datasets de alto churn de snapshots frecuentes; destruye snapshots deliberadamente.
2) Síntoma: “La latencia de la base de datos sube durante backups/replicación”
Causa raíz: Las lecturas de backups contaminan el ARC, expulsando el working set de la BD; o las escrituras de backups compiten por IOPS.
Solución: Pon backups en su propio dataset; establece primarycache=metadata para datasets de streaming; programa jobs pesados; considera un pool separado para backups.
3) Síntoma: “Las VMs tartamudean cada hora en punto”
Causa raíz: Jobs de snapshot/replicación en el dataset de VM causando picos de actividad de metadatos, o scrubs/resilvers coincidiendo con carga pico.
Solución: Ajusta la cadencia de snapshots por dataset; reparte horarios; asegura el dimensionamiento de special vdev si usas uno; corre scrub fuera de pico y monitorea impacto.
4) Síntoma: “Todo se vuelve lento cuando el pool alcanza ~85% usado”
Causa raíz: El asignador tiene menos libertad; la fragmentación sube; la amplificación de escritura aumenta, especialmente en RAIDZ con escrituras aleatorias.
Solución: Mantén margen libre; añade vdevs; mueve cargas de churn a un pool separado; poda snapshots; aplica cuotas.
5) Síntoma: “Después de activar compresión, la CPU sube y la latencia empeora”
Causa raíz: Usar un nivel/algoritmo de compresión costoso en un sistema limitado por CPU, o comprimir datos ya comprimidos/encriptados.
Solución: Usa zstd en un nivel sensato (por defecto de la plataforma); valida la holgura de CPU; considera mantener compresión activada pero evita niveles extremos.
6) Síntoma: “Las escrituras de la aplicación fallan con ENOSPC mientras zpool muestra espacio libre”
Causa raíz: Se alcanzó la cuota del dataset, o reservas en otro lugar consumen el espacio disponible.
Solución: Revisa zfs get quota,reservation; ajusta cuotas/reservas; comunica la propiedad—esto es la política funcionando, no ZFS mintiendo.
7) Síntoma: “Los archivos pequeños son dolorosamente lentos”
Causa raíz: Metadatos y bloques pequeños en discos lentos; sin special vdev; recordsize no es el factor principal—es IOPS.
Solución: Usa special vdev con suficiente capacidad; configura special_small_blocks adecuadamente; asegúrate de que atime esté desactivado; separa la carga de archivos pequeños en su propio dataset.
8) Síntoma: “La replicación no puede borrar snapshots antiguos”
Causa raíz: Holds de snapshots o cadenas incrementales dependientes que los requieren.
Solución: Revisa holds; valida la herramienta de replicación; rompe y reinicia la replicación con cuidado; no destruyas snapshots requeridos a ciegas.
Listas de comprobación / plan paso a paso
Paso a paso: migrar de “un gran dataset” a datasets por carga de trabajo
-
Inventario de cargas y puntos de montaje.
Decide fronteras basadas en comportamiento: BD, WAL, imágenes de VM, contenedores, logs, backups, homes. -
Decide el conjunto mínimo de políticas.
Para cada carga: cadencia/retención de snapshots, compresión, recordsize/volblocksize, atime, cuotas/reservas, política de caché. -
Crea datasets con propiedades explícitas.
No confíes en la herencia para ajustes críticos; la herencia está bien para valores por defecto, no para contratos. -
Mueve datos con un plan.
Para servicios en vivo, usa rsync con ventana de downtime o migración consciente de la aplicación; para datasets grandes, considera send/receive de snapshots. -
Actualiza fstab/unidades systemd.
Haz montajes explícitos y consistentes; evita sorpresas de mountpoint. -
Implementa herramientas de snapshot por clase de dataset.
Un horario para datos de BD, otro para imágenes de VM, ninguno para caches salvo que aporte valor. -
Aplica cuotas donde viva lo “temporal”.
Contenedores, CI, caches, logs. Si se puede regenerar, debe estar limitado. -
Reserva para cargas que no deben fallar.
Bases de datos y stores de estado críticos. -
Añade comprobaciones de monitorización para la deriva.
Alerta si un dataset de prod cambia async=disabled, si los conteos de snapshots explotan o si el margen del pool se reduce por debajo del umbral. -
Haz un game day.
Practica restaurar un archivo desde snapshots; practica revertir un clone de dataset; practica recibir replicación en el dataset correcto.
Checklist operacional: valores predeterminados por carga (kit inicial)
- Datos BD (archivos):
recordsize=16K,compression=zstd,atime=off, snapshots frecuentes pero de corta retención. - WAL/binlog de BD:
recordsize=16K(o específico de la carga),atime=off, snapshots normalmente deshabilitados o con retención mínima. - Imágenes de VM (archivos):
recordsize=128K,compression=zstd, snapshots diarios/semanales según RPO/RTO. - ZVOL para VM: establece
volblocksizeal crear; documentalo; trata cambios como una migración. - Contenedores: cuotas obligatorias; snapshots opcionales y usualmente cortos; considera controles de caché si el churn es alto.
- Backups/receive:
recordsize=1Ma menudo sensato,primarycache=metadata, retención larga pero replicación controlada. - Logs: compresión activada, snapshots raramente necesarios, cuotas/rotación esenciales.
Checklist de gobernanza: evitar que el caos vuelva
- Cada dataset tiene un propietario y un propósito en su nombre.
- Cada dataset de prod tiene política de snapshots explícita documentada en código/runbooks.
- Existen cuotas para todo lo no crítico o regenerable.
- Existen reservas para estado crítico donde ENOSPC es inaceptable.
- La deriva de propiedades es detectable: informes programados sobre
sync,recordsize,compression, conteos de snapshots.
Preguntas frecuentes
1) ¿“Dataset por carga” significa un dataset por aplicación?
No necesariamente. Significa un dataset por comportamiento y política. Una aplicación puede necesitar múltiples datasets (datos BD vs WAL vs uploads).
Diez apps pueden compartir un dataset si realmente comparten ciclo de vida y política (raro, pero posible).
2) ¿Cuántos datasets son demasiados?
Cuando los humanos no pueden responder “¿para qué sirve este dataset?” sin arqueología. Operativamente, ZFS maneja muchos datasets bien.
Tu límite es la nomenclatura, la propiedad y la automatización.
3) ¿Debo poner recordsize a 8K para todas las bases de datos?
No universalices. Muchos motores BD escriben en páginas de 8K/16K, pero la carga importa. Empieza con 16K para datasets de BD, mide y ajusta.
Si usas ZVOLs, recordsize no es la perilla—volblocksize lo es.
4) ¿La compresión es segura para bases de datos?
En general sí, y a menudo es beneficiosa. El riesgo es la sobrecarga de CPU en sistemas ya saturados por CPU.
La solución no es “desactivar la compresión en todas partes”; es “usar compresión sensata y monitorizar CPU”.
5) ¿Debería usar alguna vez sync=disabled?
Solo cuando aceptes explícitamente perder escrituras síncronas recientes en caso de crash/pérdida de energía—típicamente para datos temporales donde la corrección no es requerida.
Si es estado de producción, trata sync=disabled como quitar cinturones de seguridad para mejorar el tiempo de viaje.
6) ¿Por qué borrar un directorio enorme no liberó espacio inmediatamente?
Los snapshots y clones pueden mantener bloques referenciados. Revisa el uso de snapshots en ese dataset. El espacio retorna cuando desaparece la última referencia.
7) ¿Las cuotas perjudican el rendimiento?
No significativamente en entornos típicos. Las cuotas hieren sentimientos, porque obligan a planificar.
Usa cuotas para prevenir caídas del pool; es un buen intercambio.
8) Si pongo recordsize ahora, ¿reescribirá los datos existentes?
No. Afecta a escrituras nuevas. Los bloques existentes mantienen su tamaño hasta que sean reescritos por la carga normal o por una reescritura/migración explícita.
9) ¿Puedo usar un único horario de snapshots para todo y darlo por hecho?
Puedes, de la misma manera que puedes usar una sola contraseña para todo. El daño aparece más tarde, y siempre en el peor momento.
Los horarios de snapshots deben coincidir con el valor y el churn.
10) ¿Cuál es la ganancia más rápida si ya estamos en caos?
Identifica los tres principales datasets por USED y uso de snapshots, luego aplica cuotas y correcciones en políticas de snapshots.
Eso reduce el radio de daño de inmediato, incluso antes de ajustes profundos.
Conclusión: próximos pasos que realmente funcionan
El caos en ZFS rara vez es fallo tecnológico. Suele ser un fallo de gestión expresado como problema de almacenamiento.
El enfoque “dataset por carga de trabajo” te da puntos de control: intención de rendimiento, intención de seguridad e intención de capacidad.
Y te da un mapa que puedes depurar bajo presión.
Pasos prácticos:
- Dibuja tu mapa de cargas y marca donde las políticas difieren. Esas son tus fronteras de dataset.
- Crea o refactoriza datasets para que cada frontera tenga propiedades explícitas: recordsize/volblocksize, compresión, atime, snapshots, cuotas/reservas.
- Implementa una rutina de triaje rápida (capacidad → saturación → vecino ruidoso → deriva de políticas) y prácticala.
- Añade detección de deriva: alerta si un dataset de prod cambia a
sync=disabled, si los conteos de snapshots explotan o si el margen del pool se reduce por debajo de tu umbral.
Haz esto, y tu próxima alerta de “el almacenamiento está lento” deja de ser un juego de adivinanzas y pasa a ser una lista de comprobación. Ese es todo el truco.