El almacenamiento multiinquilino falla de la manera menos poética posible: el pool llega al 100%, las actualizaciones de metadatos se atascan, y de repente tu “pequeño problema”
se convierte en una interrupción con una invitación a una reunión. Un inquilino ruidoso no necesita mala intención; una caché de compilación descontrolada o un bucle de logs basta.
ZFS te da las herramientas para mantener a los inquilinos en su carril. El truco es elegir el tipo correcto de cuota, ponerla en el límite adecuado,
y entender cómo los snapshots y las reservas doblan tu modelo mental. Si te equivocas en cualquiera de esos puntos, no has hecho justicia—
solo has creado un nuevo y emocionante modo de fallo.
Objetivos de diseño: qué significa realmente “multiinquilino seguro”
La seguridad multiinquilino en ZFS no es “a todos se les asigna una cuota.” Son un conjunto de resultados explícitos:
- Un inquilino no puede llenar el pool (o si puede, lo notas temprano y el radio de impacto está acotado).
- El espacio libre del pool se mantiene por encima de un piso de seguridad para que ZFS pueda asignar, volcar y mantener la latencia razonable.
- Los inquilinos reciben errores predecibles: idealmente EDQUOT (cuota excedida), no ENOSPC (pool lleno), y no “todo está lento”.
- Operaciones puede explicar el uso de espacio sin bailar interpretativo: “este dataset es grande por snapshots” es una respuesta válida.
- La eliminación funciona cuando la necesitas. “Disco lleno y no puedes borrar” es una clásica historia de horror del almacenamiento.
Estás diseñando límites. Los datasets son esos límites. Las cuotas los hacen cumplir. Las reservas los garantizan. Y los snapshots son
los gremlins que cruzan límites y que debes tener en cuenta o solo estarás haciendo arte performativo.
Orientación con opinión: usa datasets como contenedores de inquilinos, no solo directorios. Si no puedes poner una propiedad ZFS en ello,
no puedes gobernarlo de forma fiable.
Datos interesantes y contexto histórico
- ZFS se construyó en Sun a mediados de los 2000 con integridad de datos end-to-end y almacenamiento en pool como objetivos de primera clase, no añadidos.
- Las cuotas llegaron temprano porque ZFS esperaba consolidación: múltiples consumidores compartiendo un pool, cada uno necesitando límites predecibles.
- Los snapshots son baratos de crear porque son solo metadatos al nacer; el coste aparece después por los bloques referenciados.
- “Referenced” vs “used” en los reportes de ZFS existe específicamente porque los snapshots complican “¿cuánto espacio es mío?”.
- Las reservations fueron diseñadas para la equidad y la disponibilidad: mantienen datasets críticos vivos incluso cuando el pool está bajo presión.
- Los zvols y los filesystems se gobiernan de forma diferente: las cuotas en filesystems no se mapean directamente a consumidores de zvol; las estrategias de aprovisionamiento importan.
- Históricamente, ZFS quería margen de espacio libre (a menudo 10–20%) para mantener la asignación eficiente y evitar fragmentación patológica y picos de latencia.
- OpenZFS evolucionó las herramientas (como reportes ampliados de cuotas) conforme los operadores lo desplegaron en entornos multiinquilino más grandes y ruidosos.
Primitivas de cuota: quota, refquota, reservations y por qué importan los nombres
Los límites del dataset son el límite de la política
ZFS no hace “cuotas en un árbol de directorios” de la misma manera nativa que los sistemas de ficheros tradicionales. Hace propiedades en datasets.
Eso es una característica. Te obliga a definir inquilinos reales. Un inquilino es un dataset. Todo lo demás es un detalle de implementación.
quota: limita el dataset y sus descendientes
quota limita el espacio total que un dataset puede consumir, incluyendo el espacio usado por descendientes (datasets hijos).
Esta es la herramienta correcta cuando el inquilino posee un subárbol de datasets.
Pero también es la herramienta que sorprende a la gente porque interactúa con los snapshots. Si el dataset del inquilino tiene snapshots,
los bloques retenidos por snapshots cuentan hacia el uso de una forma que puede no ser intuitiva. Si quieres “los datos en vivo del inquilino” limitados,
probablemente quieras refquota.
refquota: limita el espacio referenced (datos en vivo), no los snapshots
refquota limita el espacio referenced del dataset: los bloques actualmente alcanzables desde la cabeza del dataset.
Los snapshots no forman parte de “referenced”, así que los inquilinos no quedan bloqueados porque las instantáneas retengan espacio como rehenes.
Eso suena a magia. No lo es. El pool aún puede llenarse porque los snapshots siguen consumiendo espacio del pool. Simplemente moviste el radio de impacto:
evitaste que el inquilino reciba EDQUOT aleatorio por retención, pero no evitaste ENOSPC a nivel de pool.
reservation y refreservation: espacio garantizado, pero no gratuito
Las reservations reservan espacio que no puede usar otro. Son tu palanca de “mantener este servicio vivo”.
reservation incluye descendientes. refreservation se aplica al espacio referenced.
Las reservations pueden salvarte en un evento de presión del pool. También pueden convertir “estamos bajos” en “estamos muertos” si se usan en exceso, porque hacen
que el espacio libre parezca disponible para el pool pero no para la mayoría de datasets.
Por qué “un usuario que mata el pool” sigue ocurriendo con cuotas
Las cuotas impiden que un inquilino escriba más allá de un límite. No hacen cumplir automáticamente un piso de seguridad a nivel de pool.
Si pones cuotas que suman 200% del pool, has creado sobreaprovechamiento. Eso puede estar bien para muchas cargas.
También puede ser la forma en que aprendes qué significa “contabilidad de espacio bajo snapshots” a gran velocidad.
Idea parafraseada, atribuida: Cuando construyes sistemas, cambias problemas fáciles por problemas difíciles; el trabajo de fiabilidad es elegir los problemas difíciles que puedes supervisar.
— Charity Majors (idea parafraseada)
Además: las cuotas no reducen la amplificación de escritura. Un inquilino puede mantenerse dentro de la cuota y aun así destruir la latencia forzando fragmentación,
cargas con muchos sync o churn de bloques pequeños. Las cuotas son sobre gobernanza de capacidad, no sobre gobernanza de rendimiento. Necesitas ambos.
Broma #1: Una cuota es como una dieta—efectiva hasta que descubres que los snapshots son los bocadillos de medianoche que no registraste.
Modelos de diseño de datasets que no te odian
Modelo A: un dataset por inquilino (el ganador por defecto)
Crea pool/tenants/$tenant como un dataset filesystem. Pon todo para ese inquilino ahí.
Aplica quotas, compresión, opciones de recordsize, políticas de snapshots y puntos de montaje por inquilino.
Pros: gobernanza limpia, informes simples, baja carga cognitiva. Contras: más datasets (lo cual está bien hasta que te vuelves excesivo), y necesitas automatización.
Modelo B: dataset padre con hijos por servicio
Ejemplo: pool/tenants/acme/home, pool/tenants/acme/db, pool/tenants/acme/cache.
Pon un quota en el padre para limitar la huella total del inquilino, y refquota en hijos específicos para mantener los datos en vivo bajo control.
Este modelo te permite afinar propiedades por carga de trabajo (recordsize de base de datos, logbias, compresión) mientras aplicas un tope a nivel de inquilino.
Es un diseño maduro cuando operas servicios de plataforma.
Modelo C: directorio por inquilino dentro de un dataset (evitar)
Los administradores UNIX tradicionales aman esto porque es simple: /srv/tenants/acme, /srv/tenants/zenith.
En ZFS, es la abstracción equivocada. Pierdes la gobernanza nativa y terminas añadiendo cuotas de usuario/grupo, project quotas o herramientas externas.
Hay razones válidas—como millones de inquilinos donde el número de datasets se vuelve un problema de gestión—pero toma esa decisión con los ojos abiertos.
Para la mayoría de sistemas multiinquilino corporativos (docenas a miles), dataset-por-inquilino es más seguro y más simple.
Modelo D: zvol por inquilino (solo cuando sea necesario)
Si los inquilinos necesitan dispositivos de bloque (discos de VM, LUNs iSCSI), usarás zvols. Las cuotas en zvols son volsize.
El aprovisionamiento delgado puede sobreaprovisionar un pool fuertemente si no tienes cuidado. Para multiinquilino, debes combinar esto con monitoreo estricto
y un piso de seguridad del pool.
Snapshots: la puerta trasera silenciosa de las cuotas
Las dos “sorpresas de cuota” más comunes son:
- El inquilino alcanza su cuota incluso después de borrar un montón de archivos.
- El inquilino se mantiene por debajo de la cuota pero el pool aún se llena y todos sufren.
Cómo los snapshots interfieren con la eliminación
Si un snapshot referencia bloques que usaba un archivo, borrar el archivo del dataset en vivo no libera esos bloques. El snapshot todavía los posee.
Por eso los operadores dicen “el espacio está atrapado en snapshots”. No está atascado; está correctamente contabilizado como historia.
Si usaste quota (no refquota), los bloques retenidos por snapshots contribuyen al “used” y pueden mantener a un inquilino pegado a la cuota.
El inquilino jurará que borró cosas. Lo hizo. Tu política de retención no está de acuerdo.
Por qué refquota ayuda a los usuarios pero puede perjudicar a los pools
refquota mejora la experiencia de usuario: hace que la aplicación de la cuota siga la cabeza del dataset en vivo.
Pero desplaza el riesgo: los snapshots pueden crecer hasta que el pool esté presionado. Si eliges refquota, también debes elegir:
límites de snapshots, disciplina de retención y alertas a nivel de pool.
La retención de snapshots es política, no una estrategia de backup
Los snapshots son excelentes para rollback a corto plazo, streams de replicación y recuperación forense. No son una licencia para guardar todo para siempre
en tu pool más caliente. Trata la retención como un presupuesto: defínela, aplícala y revísala cuando el comportamiento de los inquilinos cambie.
Broma #2: Los snapshots son como los cajones de trastos de la oficina—nadie los quiere, pero todos entran en pánico cuando intentas vaciarlos.
Tareas prácticas (comandos, salidas, decisiones)
La forma más rápida de acertar con las cuotas es ejecutar el mismo pequeño conjunto de comandos cada vez, e interpretarlos de forma consistente.
A continuación hay tareas reales que puedes ejecutar en un host ZFS. Cada una incluye: comando, qué significa la salida y qué decisión tomar.
Tarea 1: Confirmar la salud del pool y si ya estás en problemas
cr0x@server:~$ zpool status -x
all pools are healthy
Significado: no hay errores conocidos del pool. Esto no significa que tengas espacio libre, ni que el rendimiento sea bueno.
Decisión: si esto no es “healthy”, arregla primero fallos de hardware/pool. Las cuotas no salvarán un pool degradado de mala latencia.
Tarea 2: Comprobar capacidad del pool, fragmentación y margen
cr0x@server:~$ zpool list -o name,size,alloc,free,cap,frag,health
NAME SIZE ALLOC FREE CAP FRAG HEALTH
tank 21.8T 18.9T 2.9T 86% 42% ONLINE
Significado: 86% usado, fragmentación en aumento. Muchos pools ZFS se vuelven desagradables por encima de ~85–90%, según la carga.
Decisión: si cap > 85%, trata las cuotas como secundarias; necesitas un plan de capacidad (borrar snapshots, añadir vdevs, mover inquilinos).
Tarea 3: Identificar primero los datasets más grandes (los sospechosos habituales)
cr0x@server:~$ zfs list -o name,used,refer,avail,mountpoint -S used | head -n 10
NAME USED REFER AVAIL MOUNTPOINT
tank/tenants/zenith 6.21T 1.02T 1.48T /srv/tenants/zenith
tank/tenants/acme 3.88T 3.62T 2.11T /srv/tenants/acme
tank/tenants/blue 2.45T 2.40T 1.90T /srv/tenants/blue
tank/backups 1.91T 1.88T 4.05T /tank/backups
tank/tenants 512K 192K 2.90T /srv/tenants
Significado: observa USED vs REFER. zenith tiene gran USED pero pequeño REFER: snapshots o descendientes poseen la diferencia.
Decisión: si USED ≫ REFER, investiga snapshots/hijos antes de gritarle al inquilino.
Tarea 4: Ver quotas y reservations aplicadas a los inquilinos
cr0x@server:~$ zfs get -r -o name,property,value,source quota,refquota,reservation,refreservation tank/tenants | head -n 25
NAME PROPERTY VALUE SOURCE
tank/tenants quota none default
tank/tenants refquota none default
tank/tenants reservation none default
tank/tenants refreservation none default
tank/tenants/acme quota 5T local
tank/tenants/acme refquota none default
tank/tenants/acme reservation none default
tank/tenants/acme refreservation none default
tank/tenants/blue quota 3T local
tank/tenants/blue refquota 2500G local
tank/tenants/blue reservation none default
tank/tenants/blue refreservation none default
tank/tenants/zenith quota 7T local
tank/tenants/zenith refquota 1500G local
tank/tenants/zenith reservation 500G local
tank/tenants/zenith refreservation none default
Significado: puedes auditar la gobernanza rápidamente. Una estrategia mixta está bien, pero debe ser intencional.
Decisión: si los inquilinos dependen de “los borrados liberan espacio”, favorece refquota más controles de snapshots. Si quieres “todo incluido”, usa quota.
Tarea 5: Establecer una cuota de tenant (límite estricto) y verificar inmediatamente
cr0x@server:~$ sudo zfs set quota=2T tank/tenants/acme
cr0x@server:~$ zfs get -o name,property,value tank/tenants/acme quota
NAME PROPERTY VALUE
tank/tenants/acme quota 2T
Significado: las escrituras que excedan 2T para ese subárbol de dataset fallarán con errores de cuota.
Decisión: si acme tiene datasets hijos, recuerda que quota los incluye. Si quieres limitar solo el dataset principal, usa refquota.
Tarea 6: Establecer refquota para “datos en vivo” y confirmar comportamiento de refer
cr0x@server:~$ sudo zfs set refquota=1500G tank/tenants/acme
cr0x@server:~$ zfs get -o name,property,value tank/tenants/acme refquota
NAME PROPERTY VALUE
tank/tenants/acme refquota 1500G
Significado: la cabeza del dataset no puede exceder 1.5T referenced. Los snapshots aún pueden crecer.
Decisión: empareja esto con retención/límites de snapshots o solo estarás posponiendo la discusión hasta que el pool esté lleno.
Tarea 7: Garantizar margen para un servicio crítico usando reservation
cr0x@server:~$ sudo zfs set reservation=200G tank/tenants/platform
cr0x@server:~$ zfs get -o name,property,value tank/tenants/platform reservation
NAME PROPERTY VALUE
tank/tenants/platform reservation 200G
Significado: 200G está reservado para ese árbol de datasets. Otros inquilinos no pueden consumirlo.
Decisión: usa reservations con moderación. Son para datasets “debe seguir funcionando”, no para consuelo político.
Tarea 8: Detectar crecimiento impulsado por snapshots en un dataset
cr0x@server:~$ zfs list -t snapshot -o name,used,refer,creation -S used tank/tenants/zenith | head -n 8
NAME USED REFER CREATION
tank/tenants/zenith@daily-2025-12-25 210G 1.02T Thu Dec 25 01:00 2025
tank/tenants/zenith@daily-2025-12-24 198G 1.01T Wed Dec 24 01:00 2025
tank/tenants/zenith@daily-2025-12-23 176G 1.00T Tue Dec 23 01:00 2025
tank/tenants/zenith@daily-2025-12-22 165G 1008G Mon Dec 22 01:00 2025
tank/tenants/zenith@daily-2025-12-21 152G 1004G Sun Dec 21 01:00 2025
tank/tenants/zenith@daily-2025-12-20 141G 1001G Sat Dec 20 01:00 2025
tank/tenants/zenith@daily-2025-12-19 135G 999G Fri Dec 19 01:00 2025
Significado: el USED de cada snapshot es el espacio único que retiene ese snapshot. El crecimiento aquí suele significar churn (reescrituras) en el dataset en vivo.
Decisión: si el USED de snapshots está hinchándose, acorta la retención, mueve cargas con mucho churn, o afina la carga (por ejemplo, deja de reescribir archivos enormes).
Tarea 9: Confirmar qué espacio está realmente disponible para un inquilino bajo cuota
cr0x@server:~$ zfs get -o name,avail,used,quota,refquota tank/tenants/acme
NAME AVAIL USED QUOTA REFQUOTA
tank/tenants/acme 320G 1.68T 2T 1500G
Significado: AVAIL refleja la restricción más estricta entre el espacio libre del pool y la aplicación de quota/refquota. Aquí refquota es probablemente el limitador.
Decisión: si AVAIL es inesperadamente pequeño, verifica si refquota es más bajo de lo previsto, o si snapshots/descendientes se cuentan vía quota.
Tarea 10: Encontrar qué hijos están consumiendo la cuota del padre del inquilino
cr0x@server:~$ zfs list -r -o name,used,refer,quota,refquota -S used tank/tenants/acme
NAME USED REFER QUOTA REFQUOTA
tank/tenants/acme 1.68T 1.45T 2T 1500G
tank/tenants/acme/cache 220G 210G none 250G
tank/tenants/acme/db 110G 108G none none
tank/tenants/acme/home 35G 34G none none
Significado: la cache es grande y está cerca de su refquota. Eso suele ser correcto: las caches deben estar acotadas.
Decisión: si la cache no está limitada, fija un refquota. Si la db es volátil, considera cuotas separadas y una reservation para mantenerla viva.
Tarea 11: Identificar si “el espacio no se libera” es por snapshots o por archivos abiertos
cr0x@server:~$ sudo zfs destroy -n tank/tenants/zenith@daily-2025-12-19
would destroy tank/tenants/zenith@daily-2025-12-19
would reclaim 135G
Significado: un destroy en modo dry-run te indica el espacio que se recuperaría si eliminas un snapshot. Esto es oro para la toma de decisiones.
Decisión: si el espacio recuperable es grande y estás en apuros, borra snapshots (empezando por los más antiguos) según la política.
Si la recuperación es pequeña, no estás persiguiendo snapshots—busca archivos abiertos y eliminados u otros datasets.
Tarea 12: Buscar archivos abiertos pero eliminados que retienen espacio (señuelo clásico de ENOSPC)
cr0x@server:~$ sudo lsof +L1 /srv/tenants/acme | head
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
java 2714 acme 12w REG 0,118 2147483648 0 553211 /srv/tenants/acme/logs/app.log (deleted)
Significado: el proceso todavía mantiene un descriptor de archivo hacia un archivo eliminado. El espacio no se liberará hasta que el proceso lo cierre.
Decisión: reinicia o envía la señal adecuada para que el proceso rote logs. No borres más archivos; solo crearás más “eliminados pero abiertos.”
Tarea 13: Confirmar puntos de montaje y evitar “escribir fuera del dataset que crees”
cr0x@server:~$ zfs get -o name,mountpoint,canmount tank/tenants/acme
NAME MOUNTPOINT CANMOUNT
tank/tenants/acme /srv/tenants/acme on
Significado: si los mountpoints son erróneos, los inquilinos pueden escribir en el dataset padre (sin cuota) y eludir los límites.
Decisión: verifica el punto de montaje de cada inquilino y asegúrate de que los datasets padre no sean escribibles por inquilinos.
Tarea 14: Aplicar “guardarraíles aburridos” en el dataset padre
cr0x@server:~$ sudo zfs set readonly=on tank/tenants
cr0x@server:~$ zfs get -o name,readonly tank/tenants
NAME PROPERTY VALUE
tank/tenants readonly on
Significado: los inquilinos no pueden escribir accidentalmente en el mount padre (si es que está montado), forzando las escrituras hacia los datasets de inquilinos.
Decisión: para multiinquilino, haz los padres no escribibles y usa mountpoints hijos explícitos. Previene eludir límites por accidente.
Tarea 15: Monitorizar la presión lógica de espacio por dataset (cuota acercándose)
cr0x@server:~$ zfs list -o name,used,quota,refquota,available -r tank/tenants | awk 'NR==1 || $3!="none" || $4!="none"{print}'
NAME USED QUOTA REFQUOTA AVAIL
tank/tenants/acme 1.68T 2T 1500G 320G
tank/tenants/blue 2.45T 3T 2500G 550G
tank/tenants/zenith 6.21T 7T 1500G 790G
Significado: una vista rápida de datasets gobernados. AVAIL te da un indicador de “¿fallarán las escrituras pronto?”.
Decisión: alerta por %used de la cuota y también por cap del pool. Un inquilino puede estar bien mientras el pool no lo esté.
Tarea 16: Para inquilinos zvol, verificar el riesgo del aprovisionamiento delgado
cr0x@server:~$ zfs list -t volume -o name,volsize,used,refer,logicalused,logicalrefer -S logicalused
NAME VOLSIZE USED REFER LOGICALUSED LOGICALREFER
tank/vm/tenant01 800G 120G 120G 640G 640G
tank/vm/tenant02 800G 160G 160G 790G 790G
Significado: logicalused muestra lo que el invitado cree haber usado; USED es lo que el pool realmente asignó.
El aprovisionamiento delgado oculta el riesgo hasta que deja de hacerlo.
Decisión: si logicalused se acerca a volsize en muchos inquilinos, trátalo como presión real de capacidad y presupuestiza espacio en consecuencia.
Guía rápida de diagnóstico
Cuando un pool multiinquilino está en problemas, no tienes tiempo para la pureza filosófica. Necesitas un camino rápido a: “¿qué está llenando qué?”
y “¿es esto capacidad o rendimiento?”
Primero: confirma si tienes una emergencia a nivel de pool
- Capacidad del pool:
zpool list -o name,alloc,free,cap,frag. Si cap > 90%, asume que todo se volverá extraño. - Salud del pool:
zpool status. Si está degradado, espera peor latencia y borrados más lentos. - Candidatos inmediatos para recuperar espacio:
zfs list -t snapshot -o name,used -S used.
Segundo: identifica si el problema es “cuota alcanzada” o “pool lleno”
- Si los inquilinos ven errores como “Disk quota exceeded,” estás tratando con gobernanza a nivel de dataset.
- Si todos ven “No space left on device,” estás ante agotamiento del pool o inanición por reservations.
- Comprueba
zfs get avail,quota,refquotaen el dataset afectado y compáralo con el espacio libre del pool.
Tercero: decide snapshots vs archivos abiertos vs otro dataset
- Snapshots: si USED ≫ REFER en el dataset, lista snapshots y haz un destroy en modo dry-run para estimar recuperación.
- Archivos abiertos pero eliminados: ejecuta
lsof +L1en el punto de montaje. Si aparecen, reinicia al culpable. - Punto de montaje equivocado / bypass: verifica los mountpoints y comprueba si las escrituras fueron al dataset padre sin cuota.
Cuarto: si el síntoma es rendimiento, no lo confundas con capacidad
- Alta fragmentación + alto cap puede parecer “problemas de cuota” porque las escrituras tardan o se bloquean.
- Mide la presión IO con
zpool iostat -v 1y busca vdevs saturados. - Si estás cerca del lleno, tu mejor “afinamiento de rendimiento” es liberar espacio.
Tres mini-historias corporativas desde las trincheras de las cuotas
Mini-historia 1: la interrupción causada por una suposición errónea
Una empresa de tamaño medio ejecutaba un pool ZFS compartido para equipos internos: analytics, sistemas de build, algunas propiedades web. Hicieron lo sensato:
dataset por equipo, quotas en cada dataset. Estaban orgullosos. El pool era estable. Luego un lunes, la mitad de los trabajos de CI fallaron con ENOSPC.
El on-call asumió que un equipo había excedido su cuota. Pero las cuotas estaban bien. Cada dataset de equipo aún tenía margen.
El pool, sin embargo, estaba al 98%, y ZFS se comportaba como un sistema de almacenamiento al 98%: la asignación se volvió costosa y las actualizaciones de metadatos se ralentizaron.
La suposición errónea fue sutil: “Si cada equipo tiene una cuota, el pool no puede llenarse.” Las cuotas no se suman solas en seguridad.
Habían sobreaprovisionado—silenciosamente—porque las cuotas se establecieron según expectativas del negocio, no según la capacidad real del pool, y la retención no estaba acotada.
El verdadero culpable: snapshots automáticos mantenidos “por un tiempo”, que lentamente se convirtieron en “para siempre” porque nadie quería borrar historial.
Un solo equipo con carga de alto churn (artefactos grandes reescritos diariamente) causó el crecimiento de snapshots. Sus datos en vivo se mantuvieron bajo refquota,
pero los snapshots comieron el pool de forma sostenida.
La solución no fue heroica. Definieron retención de snapshots por clase de inquilino, añadieron límites de cantidad de snapshots, y configuraron una alerta de seguridad del pool en 80/85/90%.
También empezaron una revisión mensual de datasets donde USED-REFER excedía un umbral. Aburrido, consistente y efectivo.
Mini-historia 2: la optimización que salió mal
Otra empresa ofrecía “sandboxes para desarrolladores” en ZFS. Querían una gran experiencia de desarrollador, así que cambiaron muchos datasets de inquilino
de quota a refquota. El objetivo: dejar de escuchar que los devs se quejaban porque borrar archivos no restauraba su capacidad
debido a snapshots que retenían espacio.
Funcionó. Las quejas bajaron. El equipo de plataforma celebró con la clase de satisfacción silenciosa que solo obtienes al eliminar toda una categoría
de tickets. Y entonces el pool comenzó a llenarse más rápido de lo esperado, pero nadie lo notó inmediatamente porque los dashboards de inquilinos se veían bien.
El contratiempo vino por la visibilidad. Con refquota, los inquilinos nunca alcanzaban su “límite” porque sus datos en vivo permanecían acotados,
mientras los snapshots crecían sin control. El sistema había desplazado el fallo de “el inquilino no puede escribir” a “pool lleno,”
que es un fallo mucho peor en terreno multiinquilino.
El incidente terminó de la manera habitual: borraron snapshots bajo presión, la replicación se retrasó y algunas restauraciones se volvieron imposibles.
No catastrófico, pero doloroso y evitable.
La solución fue tratar la retención de snapshots como parte de la gobernanza de cuotas. Implementaron:
límites de snapshots por dataset, horarios de snapshots por inquilino, y un informe que clasificaba inquilinos por “espacio solo de snapshots.”
Refquota se quedó—pero solo con guardarraíles y un piso de espacio libre del pool.
Mini-historia 3: la práctica aburrida pero correcta que salvó el día
Una organización regulada operaba un clúster ZFS multiinquilino para equipos de aplicaciones. Los ingenieros de almacenamiento eran alérgicos a las sorpresas,
así que hicieron dos cosas poco sexys: mantuvieron 20% de espacio libre como política, y reservaron una pequeña porción para datasets de plataforma
(logs, autenticación, colas de monitoring).
En un cierre de trimestre, el job por lotes de un equipo empezó a producir mucho más output de lo normal. El dataset del inquilino alcanzó su quota.
La app falló ruidosamente—exactamente lo que quieres. El pool se mantuvo sano, el monitoring siguió en línea y otros equipos no lo notaron.
El on-call recibió una alerta clara: “quota de inquilino excedida.” No “pool lleno.” No “latencia IO 10x.” No “todo arde.”
Aumentaron la cuota del inquilino temporalmente, pero solo después de mover snapshots antiguos a un pool más frío y recortar la retención.
La clave no fue la cuota por sí sola. Fue la combinación: un piso de seguridad del pool, reservas para servicios esenciales, e informes consistentes.
El incidente quedó acotado al inquilino. Ese es el objetivo de la ingeniería multiinquilino.
Errores comunes: síntomas → causa raíz → solución
1) Síntoma: “Borré 500GB pero sigo en cuota”
Causa raíz: snapshots todavía referencian los bloques borrados; la aplicación de quota los cuenta.
Solución: o bien borra/expira snapshots, o cambia a refquota para ese dataset y controla los snapshots por separado.
2) Síntoma: el inquilino está bajo cuota, pero el pool llega al 100% igualmente
Causa raíz: refquota limita solo los datos en vivo; snapshots, otros datasets y el aprovisionamiento delgado de zvols siguen consumiendo espacio del pool.
Solución: aplica retención/límites de snapshots, monitoriza el crecimiento “solo snapshots” (USED-REFER), y mantén un piso de espacio libre a nivel de pool.
3) Síntoma: ENOSPC aleatorio aunque zpool list muestre espacio libre
Causa raíz: reservations u otras restricciones de asignación significan que el espacio libre no es utilizable para ese dataset.
Solución: audita reservation/refreservation; reduce o elimina reservations no críticas; asegura que los datasets críticos tengan las reservations, no todo el mundo.
4) Síntoma: el inquilino puede escribir fuera de la cuota de alguna manera
Causa raíz: las escrituras aterrizan en un dataset padre (mountpoint incorrecto, confusión de bind-mount, o permisos en el mount padre).
Solución: bloquea datasets padre (readonly=on, canmount=off donde corresponda), verifica mountpoints y restringe permisos.
5) Síntoma: el pool no está lleno, pero la latencia es terrible y las escrituras se arrastran
Causa raíz: alta fragmentación, churn de bloques pequeños, cargas con muchos sync, o un vdev degradado; la gobernanza de capacidad no soluciona saturación IO.
Solución: mantén margen libre, separa cargas con mucho churn en sus propios vdevs/pools, y mide con zpool iostat. Considera SLOG/special vdevs cuando proceda.
6) Síntoma: “el espacio no se libera” después de borrar archivos grandes, no hay snapshots encontrados
Causa raíz: archivos abiertos pero eliminados sostenidos por procesos.
Solución: lsof +L1 para encontrar culpables; reinicia o rota logs correctamente.
7) Síntoma: la replicación del inquilino crece sin un aparente crecimiento en vivo
Causa raíz: reescrituras frecuentes crean muchos deltas de snapshot; los streams de send crecen incluso si los datos en vivo se mantienen estables.
Solución: reduce el churn (cambios en la app), ajusta la frecuencia de snapshots, o mueve ese inquilino a un pool diseñado para churn.
Listas de verificación / plan paso a paso
Paso a paso: configurar un nuevo inquilino de forma segura
- Crea un dataset por inquilino (o por inquilino/servicio si necesitas propiedades diferentes).
- Establece el mountpoint explícitamente y asegura que los datasets padre no sean escribibles por inquilinos.
- Elige el modelo de cuota:
- Usa
quotasi los snapshots cuentan como “su problema” y quieres límite total estricto. - Usa
refquotasi quieres que los usuarios vean su uso de datos en vivo y gestionas snapshots centralmente.
- Usa
- Decide la política de snapshots: frecuencia y retención. Ponla en código, no en memoria tribal.
- Añade alertas: %used de cuota, umbrales de cap del pool y crecimiento “solo snapshots”.
- Documenta el modo de fallo que verá el inquilino: EDQUOT vs ENOSPC y qué debe hacer.
Paso a paso: hacer cumplir el piso de seguridad del pool (el plan “no me despierte a las 2”)
- Elige un objetivo de espacio libre (comúnmente 10–20% dependiendo de la carga y el layout de vdevs).
- Alerta temprano en múltiples umbrales (por ejemplo, 80/85/90%), no solo al 95% cuando ya es miserable.
- Audita el sobreaprovechamiento: suma de quotas vs tamaño del pool; acepta sobreaprovechamiento solo si puedes explicar por qué es seguro.
- Limita el crecimiento de snapshots: límites de retención y (donde tu herramienta lo permita) límites de cantidad/espacio por inquilino.
- Mantén reservations para datasets de plataforma: monitoring, logs y metadatos de autenticación no deben competir con inquilinos durante un incidente.
Paso a paso: responder cuando el pool está cerca del lleno
- Detén la hemorragia: identifica la recuperación más rápida (normalmente snapshots) y confirma la recuperación con
zfs destroy -n. - Si los inquilinos están escribiendo fuera de cuotas, corrige mountpoints y permisos de inmediato.
- Revisa archivos abiertos pero eliminados y reinicia a los culpables.
- Recorta temporalmente la retención de snapshots, luego restaura una política sensata con aprobaciones.
- Programa expansión de capacidad o migración de datos; “seremos cuidadosos” no es un plan de capacidad.
Preguntas frecuentes
1) ¿Debería usar quota o refquota para inquilinos?
Si los inquilinos gestionan sus propios snapshots o quieres limitar la “huella total incluyendo historial”, usa quota.
Si gestionas snapshots centralmente y quieres que la experiencia de usuario refleje los datos en vivo, usa refquota, pero entonces debes gobernar el crecimiento de snapshots por separado.
2) ¿Pueden las cuotas evitar que un pool llegue al 100%?
No por sí solas. Las cuotas limitan datasets. El agotamiento a nivel de pool aún ocurre por snapshots, otros datasets, aprovisionamiento delgado de zvols,
reservations y sobreaprovechamiento. Aún necesitas una política de margen de pool y alertas.
3) ¿Por qué USED difiere tanto de REFER?
REFER es el espacio referenciado por la cabeza del dataset (vista en vivo). USED incluye bloques retenidos por snapshots y descendientes.
Una gran brecha suele significar snapshots o datasets hijos.
4) ¿Qué error verán las aplicaciones cuando se alcanza una cuota?
Típicamente “Disk quota exceeded” (EDQUOT). Si el pool en sí está sin espacio, verán “No space left on device” (ENOSPC),
lo cual afecta a todos y es operacionalmente mucho peor.
5) Si borro snapshots, ¿siempre recuperaré espacio inmediatamente?
Normalmente sí, pero la cantidad recuperada depende del compartido de bloques. Usa zfs destroy -n snapshot para estimar la recuperación.
Si la recuperación es pequeña, ese snapshot no es tu problema principal.
6) ¿Las reservations son una buena forma de “proteger” a cada inquilino?
No. Las reservations son para proteger servicios críticos, no para hacer que todos se sientan seguros. Usar reservations en exceso puede dejar al pool sin espacio
y causar comportamientos ENOSPC confusos incluso cuando el pool reporta espacio libre.
7) ¿Cómo evito que los inquilinos eludan cuotas escribiendo en otro sitio?
Usa un dataset por inquilino con mountpoints, haz los datasets padre no escribibles, verifica mountpoint y canmount,
y asegura que los permisos no permitan escrituras en padres compartidos.
8) ¿Los snapshots cuentan contra refquota?
No, ese es el punto. Los snapshots siguen contando contra el pool, eso sí. Refquota es un tope por dataset para datos en vivo, no un mecanismo de seguridad de pool.
9) ¿Cuál es el patrón multiinquilino más simple que funciona en producción?
Un dataset por inquilino, un modelo de cuota claro (quota o refquota), snapshots automatizados con retención estricta,
y alertas tanto en límites de inquilino como en margen de pool. Mantenlo aburrido.
Conclusión: siguientes pasos para evitar la llamada a las 2 a.m.
Las cuotas ZFS no son algo opcional; son la forma de evitar que un inquilino convierta almacenamiento compartido en un incidente compartido.
Pero las cuotas solo funcionan cuando el diseño de datasets coincide con tu modelo de tenencia, y cuando los snapshots y las reservations se tratan como política de primera clase.
Pasos prácticos:
- Audita tus límites de inquilinos: si los inquilinos son directorios, planifica migrar a dataset-por-inquilino.
- Elige la semántica de cuota intencionalmente:
quotapara huella total,refquotapara UX de datos en vivo—luego implementa los guardarraíles faltantes. - Implementa límites de retención de snapshots y un informe de crecimiento “solo snapshots”.
- Establece un piso de espacio libre del pool y alerta antes del 85% de uso; no esperes al 95% para descubrir la física.
- Reserva espacio solo para datasets críticos de plataforma para que puedas operar cuando los inquilinos se porten mal.
Haz esto bien y “un usuario mató el pool” se convierte en una historia que cuentas a los nuevos empleados como advertencia, no en una tradición trimestral.