Cuotas ZFS para multiinquilinos: evitar que un usuario deje el pool sin espacio

¿Te fue útil?

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,refquota en 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 +L1 en 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 1 y 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

  1. Crea un dataset por inquilino (o por inquilino/servicio si necesitas propiedades diferentes).
  2. Establece el mountpoint explícitamente y asegura que los datasets padre no sean escribibles por inquilinos.
  3. Elige el modelo de cuota:
    • Usa quota si los snapshots cuentan como “su problema” y quieres límite total estricto.
    • Usa refquota si quieres que los usuarios vean su uso de datos en vivo y gestionas snapshots centralmente.
  4. Decide la política de snapshots: frecuencia y retención. Ponla en código, no en memoria tribal.
  5. Añade alertas: %used de cuota, umbrales de cap del pool y crecimiento “solo snapshots”.
  6. 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”)

  1. Elige un objetivo de espacio libre (comúnmente 10–20% dependiendo de la carga y el layout de vdevs).
  2. Alerta temprano en múltiples umbrales (por ejemplo, 80/85/90%), no solo al 95% cuando ya es miserable.
  3. Audita el sobreaprovechamiento: suma de quotas vs tamaño del pool; acepta sobreaprovechamiento solo si puedes explicar por qué es seguro.
  4. Limita el crecimiento de snapshots: límites de retención y (donde tu herramienta lo permita) límites de cantidad/espacio por inquilino.
  5. 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

  1. Detén la hemorragia: identifica la recuperación más rápida (normalmente snapshots) y confirma la recuperación con zfs destroy -n.
  2. Si los inquilinos están escribiendo fuera de cuotas, corrige mountpoints y permisos de inmediato.
  3. Revisa archivos abiertos pero eliminados y reinicia a los culpables.
  4. Recorta temporalmente la retención de snapshots, luego restaura una política sensata con aprobaciones.
  5. 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:

  1. Audita tus límites de inquilinos: si los inquilinos son directorios, planifica migrar a dataset-por-inquilino.
  2. Elige la semántica de cuota intencionalmente: quota para huella total, refquota para UX de datos en vivo—luego implementa los guardarraíles faltantes.
  3. Implementa límites de retención de snapshots y un informe de crecimiento “solo snapshots”.
  4. Establece un piso de espacio libre del pool y alerta antes del 85% de uso; no esperes al 95% para descubrir la física.
  5. 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.

← Anterior
Sparing dRAID de ZFS: Cómo los repuestos distribuidos cambian la recuperación
Siguiente →
WireGuard «Handshake did not complete»: localizar problemas de NAT, puertos y tiempo

Deja un comentario