La primera señal nunca es “el pool se está muriendo”. Es un mensaje en Slack: “¿Por qué la base de datos se congela aleatoriamente?”
Luego un tablero: la latencia p99 se convierte en arte moderno. La CPU está bien. La red está bien. ZFS dice “ONLINE.”
Y aun así, cada pocos minutos tu pool solo SSD choca contra un muro como si hubiera olvidado cómo escribir.
Ese muro suele ser la recolección de basura de los SSD encontrándose con los patrones de escritura de ZFS, y multiplicando el dolor mediante escrituras sync,
bloques pequeños o fragmentación accidental. Esto no es teórico. Ocurre en producción, a las 2 a.m., justo después de que alguien
“optimizó” algo.
Cómo se manifiesta el “colapso por garbage collection” en ZFS
Los SSD no sobrescriben en el mismo lugar. Escriben en páginas nuevas y más tarde limpian las antiguas. Esa limpieza es la garbage collection (GC),
y normalmente está oculta tras el firmware del controlador y el área de reserva. ZFS tampoco sobrescribe en el mismo lugar. Es copy-on-write.
Así que tienes dos capas de “escribo en otro sitio y limpio después” apiladas. Cuando las condiciones son las adecuadas (o inadecuadas),
esas capas se sincronizan en una miseria periódica.
En producción verás:
- Picos de latencia sin un cuello de botella de CPU obvio. El sistema está “idle” mientras el almacenamiento se derrite.
- Colapso de IOPS de escritura tras escrituras aleatorias sostenidas, especialmente cuando el pool está muy utilizado.
- Cargas con muchas operaciones sync (bases de datos, NFS, hipervisores) se bloquean de repente a pesar de que “es NVMe”.
- zpool iostat muestra esperas enormes mientras el ancho de banda parece modesto.
- Los picos se correlacionan con commits de transaction group (TXG) o ráfagas periódicas de escrituras pequeñas.
La clave: el colapso por GC no es “ZFS es lento”. Es un lazo de retroalimentación a nivel de sistema: ZFS produce un patrón de escritura; el firmware del SSD reacciona;
la latencia aumenta; ZFS encola más; el firmware tiene menos tiempo inactivo; la latencia aumenta de nuevo. Es como un atasco donde todos
deciden cambiar de carril al mismo tiempo.
Broma #1: la garbage collection del SSD es como limpiar tu apartamento metiendo todo en un armario—hasta que el armario empieza a cobrar alquiler.
Datos interesantes y contexto histórico
- TRIM no siempre fue un estándar. Los primeros despliegues de SSD a menudo funcionaban sin TRIM/discard efectivo, por lo que las unidades “olvidaban” qué páginas estaban libres y el rendimiento se degradaba con el tiempo.
- El copy-on-write de ZFS precede la ubicuidad de los SSD. ZFS se diseñó para integridad de datos y grandes almacenamientos; el comportamiento de la flash se convirtió en una preocupación de ajuste más tarde, cuando la flash se volvió la opción por defecto.
- La amplificación de escritura es un doble impuesto. Los SSD ya reescriben estructuras internas; los sistemas de archivos CoW pueden aumentar el movimiento interno si la carga fragmenta el espacio libre.
- Los SSD empresariales no son simplemente “más rápidos”. Normalmente vienen con más overprovisioning, mejor comportamiento en steady-state y firmware ajustado para cargas mixtas.
- La protección contra pérdida de energía (PLP) cambió la historia del SLOG. En el momento en que confías en la semántica sync, los SSD con condensadores se vuelven un requisito de diseño, no un lujo.
- Los errores de alineación 4K son antiguos y todavía ocurren. La industria pasó de sectores de 512 bytes a 4K físicos, y la desalineación sigue destruyendo el rendimiento en silencio.
- NVMe resolvió un problema de colas, no de física. Colas profundas y bajo overhead ayudan, pero los bloques de borrado de la flash y la GC siguen existiendo; los acantilados de latencia siguen siendo reales.
- La compresión en ZFS se volvió una herramienta de rendimiento en SSD. Con LZ4, escribir menos NAND puede significar más throughput y menos estrés de GC, no solo ahorro de espacio.
- Los special vdevs son un arma moderna con filo. Pueden ser asombrosos para metadata/bloques pequeños, pero el dominio de fallo y los errores de dimensionamiento han tumbado más de unos cuantos pools “rápidos”.
Un modelo mental que realmente predice fallos
1) ZFS escribe en transaction groups, no al ritmo de tu aplicación
Las aplicaciones emiten escrituras. ZFS acumula datos “dirty” en memoria (ARC) y los vacía en transaction groups (TXGs).
Esos commits son por ráfagas. Las escrituras por ráfagas están bien para los SSD hasta que el SSD tiene pocas páginas limpias y necesita hacer GC
justo cuando lo estás martillando. Entonces la ráfaga se convierte en un bloqueo.
2) Los SSD tienen un “steady-state” que el marketing no imprime
El rendimiento recién sacado de la caja no es el rendimiento en steady-state. Después de que la unidad se llena y reescribe, la amplificación de escritura sube y
el rendimiento sostenido de escritura aleatoria puede caer dramáticamente. Un pool al 80–90% de uso es el escenario clásico: menos espacio libre significa
menos opciones fáciles tanto para el asignador de ZFS como para el FTL del SSD.
3) Las escrituras sync son las mejores amigas de la latencia (desafortunadamente)
Si tu carga requiere escrituras sync, ZFS debe confirmar durabilidad en almacenamiento estable antes de reconocer.
Sin un dispositivo de log separado (SLOG) que acepte escrituras de baja latencia de forma segura, tus dispositivos del pool cargan con la tarea.
Si esos dispositivos están ocupados con GC al mismo tiempo, tu p99 se convierte en tu personalidad.
4) La fragmentación no es solo “archivos dispersos”
En ZFS, la fragmentación trata sobre segmentación del espacio libre y patrones de asignación de bloques. En los SSD, se acopla con el propio mapeo del FTL.
Alta fragmentación dificulta escribir segmentos contiguos grandes; el SSD termina haciendo más copias internas durante la GC.
El resultado clásico es: “pero es un SSD, ¿por qué la escritura secuencial es solo 150 MB/s?”
5) La compresión puede ser una perilla de ajuste para SSD
La compresión no es solo para ahorrar espacio. Si comprimes, escribes menos bytes, lo que puede reducir la amplificación de escritura.
En CPUs modernas, LZ4 suele ser una ganancia neta para throughput y latencia. La excepción es datos ya comprimidos o cuando la CPU es el cuello de botella.
Una idea parafraseada de Werner Vogels (CTO de Amazon): “Todo falla, todo el tiempo—diseña para poder sobrevivirlo.”
ZFS en SSD es exactamente eso: asume que los acantilados de latencia existen y diseña en torno a ellos.
Guía rápida de diagnóstico (primero/segundo/tercero)
Primero: confirma que el síntoma es latencia de almacenamiento, no CPU, ni red
- Revisa el desglose de latencia de la aplicación (tiempo de commit de la BD, tiempo de fsync, latencia de disco en VMs).
- Comprueba carga del sistema vs iowait: alta carga con bajo uso de CPU a menudo significa bloqueo por IO.
- Confirma que ZFS es el dispositivo esperado que genera espera, no un montaje de red a valle o una cola de NIC saturada.
Segundo: determina si el problema es sync, fragmentación o steady-state del dispositivo
- Si los bloqueos se alinean con fsync/commit: sospecha la ruta de escritura sync (SLOG, ajustes de sync, PLP).
- Si el rendimiento empeora conforme el pool se llena: sospecha espacio libre + fragmentación y presión de GC en los SSD.
- Si “las escrituras aleatorias colapsan después de minutos”: sospecha el steady-state del SSD y falta de overprovisioning/TRIM.
Tercero: aísla qué capa está colapsando
- Capa ZFS: temporización de TXG, límites de datos sucios, desajuste recordsize/volblocksize, saturación de special vdev.
- Capa de dispositivo: GC del firmware, throttling térmico, errores de media, saturación de colas internas.
- Capa de topología: RAIDZ muy ancho en SSDs para tu perfil de IOPS, o cuello de botella por vdev único.
Si te llevas un solo hábito operacional de esto: durante un incidente, recopila evidencia primero.
Ajustar a ciegas es como acabar con un “arreglo de rendimiento” que luego se convierte en un postmortem por pérdida de datos.
Tareas prácticas: comandos, salidas, decisiones (12+)
Task 1: Check pool health and topology (you’re not tuning a broken system)
cr0x@server:~$ zpool status -v ssdpool
pool: ssdpool
state: ONLINE
scan: scrub repaired 0B in 00:12:19 with 0 errors on Sun Dec 22 03:10:11 2025
config:
NAME STATE READ WRITE CKSUM
ssdpool ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
nvme-SAMSUNG_MZVLB1T0HBLR-00000 ONLINE 0 0 0
nvme-SAMSUNG_MZVLB1T0HBLR-00001 ONLINE 0 0 0
errors: No known data errors
Qué significa: Si estás degradado o en resilver, los datos de rendimiento están contaminados. Los mirrors se comportan diferente que RAIDZ.
Decisión: No ajustes hasta que el pool esté estable y los scrubs estén limpios. Si la topología es RAIDZ y la carga es de escrituras sync-aleatorias intensas, reconsidera el diseño.
Task 2: Watch real-time latency and queueing per vdev
cr0x@server:~$ zpool iostat -v ssdpool 1
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
ssdpool 620G 280G 120 1800 12.3M 210M
mirror-0 620G 280G 120 1800 12.3M 210M
nvme0n1 - - 60 900 6.1M 105M
nvme1n1 - - 60 900 6.2M 105M
-------------------------- ----- ----- ----- ----- ----- -----
Qué significa: Esto muestra tasa, no latencia directamente. Si las ops caen mientras la latencia de la aplicación sube, el dispositivo está entrando en stall.
Decisión: Si un vdev rinde menos o de forma desigual, sospecha firmware del dispositivo/problemas térmicos o un path con comportamiento anómalo.
Task 3: Check detailed per-device latency (OpenZFS on Linux)
cr0x@server:~$ cat /proc/spl/kstat/zfs/vdev_queue | head -n 25
nthreads 4
vdev_queue_max_active 1000
vdev_queue_min_active 1
vdev_queue_max_pending 1000
vdev_queue_min_pending 1
vdev_queue_max_threads 8
vdev_queue_min_threads 1
vdev_queue_agg_lim 131072
vdev_queue_read_gap_limit 32768
vdev_queue_write_gap_limit 65536
...
Qué significa: Existe ajuste de colas, pero rara vez es la primera palanca. Los valores por defecto suelen estar bien a menos que tengas un patrón específico de contención.
Decisión: No empieces por tocar internals de colas. Primero confirma que no estás provocando colapso de GC por espacio, sync y dimensionado de bloques.
Task 4: Confirm autotrim state and enable it if appropriate
cr0x@server:~$ zpool get autotrim ssdpool
NAME PROPERTY VALUE SOURCE
ssdpool autotrim off default
Qué significa: Con autotrim off, los bloques liberados pueden no comunicarse prontamente a los SSD. Algunas unidades lo manejan; otras decaen lentamente hacia un steady-state de bajo rendimiento.
Decisión: Para pools solo SSD, habilita autotrim salvo que tengas un problema conocido de firmware/discard con la unidad.
cr0x@server:~$ sudo zpool set autotrim=on ssdpool
Task 5: Verify discard/TRIM is actually reaching the device (Linux)
cr0x@server:~$ lsblk -D
NAME DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
nvme0n1 0 4K 2G 0
nvme1n1 0 4K 2G 0
Qué significa: DISC-GRAN muestra la granularidad de discard. Si es 0B, el dispositivo/pila puede no soportar discard.
Decisión: Si discard no está soportado, autotrim no ayudará. Planea más overprovisioning y mantén la utilización del pool más baja.
Task 6: Check pool utilization and fragmentation
cr0x@server:~$ zpool list -o name,size,alloc,free,cap,frag,ashift,health ssdpool
NAME SIZE ALLOC FREE CAP FRAG ASHIFT HEALTH
ssdpool 932G 620G 312G 66% 18% 12 ONLINE
Qué significa: CAP y FRAG se correlacionan con el dolor del asignador y la presión de GC. En pools solo SSD, los problemas suelen aparecer al acercarse a alta CAP.
Decisión: Trata 80%+ como “zona de peligro” para escrituras aleatorias mixtas. Si debes operar «caliente», usa unidades con OP serio y diseña para ello.
Task 7: Identify datasets with bad recordsize for the workload
cr0x@server:~$ zfs get -r recordsize,compression,atime ssdpool/app
NAME PROPERTY VALUE SOURCE
ssdpool/app recordsize 128K default
ssdpool/app compression lz4 local
ssdpool/app atime off local
Qué significa: recordsize 128K está bien para IO de streaming y muchas cargas generales. Puede ser fatal para actualizaciones aleatorias pequeñas (bases de datos) al aumentar la amplificación de escritura.
Decisión: Para ficheros de BD o imágenes de VM, considera 16K o 8K recordsize (datasets) o el volblocksize apropiado (zvols), luego haz benchmark y observa.
Task 8: For zvols, check volblocksize and alignment expectations
cr0x@server:~$ zfs get volblocksize,compression,logbias,sync ssdpool/vmstore
NAME PROPERTY VALUE SOURCE
ssdpool/vmstore volblocksize 8K local
ssdpool/vmstore compression lz4 inherited from ssdpool
ssdpool/vmstore logbias latency default
ssdpool/vmstore sync standard default
Qué significa: volblocksize se fija al crear. Un zvol de 8K suele ser sensato para IO aleatorio de VM, pero puede aumentar metadata y overhead de IO según la carga.
Decisión: Empareja volblocksize con el tamaño de página del filesystem/BD invitado y la carga. Si está mal, recrea el zvol. No hay un interruptor mágico después.
Task 9: Check whether sync writes are dominating
cr0x@server:~$ sudo zpool iostat -v ssdpool 1 5
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
ssdpool 620G 280G 100 2400 10.1M 140M
mirror-0 620G 280G 100 2400 10.1M 140M
nvme0n1 - - 50 1200 5.0M 70M
nvme1n1 - - 50 1200 5.1M 70M
-------------------------- ----- ----- ----- ----- ----- -----
cr0x@server:~$ sudo cat /proc/spl/kstat/zfs/zil | head
zil_commit_count 98122
zil_commit_writer_count 98050
zil_commit_waiter_count 72
zil_commit_time 19123456789
zil_commit_lwb_count 220001
Qué significa: Un ZIL muy activo indica muchas semánticas synchronous. Eso es normal para algunas cargas.
Decisión: Si necesitas durabilidad sync, considera un SLOG adecuado con PLP. Si no lo necesitas, corrige las opciones de montaje/exportación de la aplicación en lugar de engañar a ZFS.
Task 10: Verify SLOG presence and whether it’s actually helping
cr0x@server:~$ zpool status ssdpool
pool: ssdpool
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
ssdpool ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
nvme0n1 ONLINE 0 0 0
nvme1n1 ONLINE 0 0 0
logs
nvme2n1 ONLINE 0 0 0
Qué significa: Existe un dispositivo de log. Eso no significa que sea seguro o rápido. Si carece de PLP, una pérdida de energía puede convertir “sync rápido” en “corrupción creativa.”
Decisión: Usa SLOGs diseñados para protección contra pérdida de energía. Si no puedes garantizarlo, acepta mayor latencia sync o re-arquitecta.
Task 11: Inspect dirty data behavior (TXG pressure)
cr0x@server:~$ cat /proc/spl/kstat/zfs/arcstats | egrep 'dirty|txg|memory_throttle' | head -n 12
dirty_data 1879048192
dirty_over_target 0
memory_throttle_count 12
txg_syncing 5321
txg_synced 5320
Qué significa: Si memory_throttle_count sube, ZFS está obligando a los escritores a esperar porque hay demasiados datos sucios.
Eso suele correlacionar con almacenamiento que no mantiene el ritmo (o stalls periódicos).
Decisión: Si el sync de TXG es lento porque los dispositivos stallan, arregla las causas de dispositivo/GC primero. Ajustar límites de sucios puede enmascarar síntomas pero no curar la enfermedad.
Task 12: Check for thermal throttling on NVMe (the silent performance killer)
cr0x@server:~$ sudo nvme smart-log /dev/nvme0n1 | egrep 'temperature|warning|critical'
temperature : 77 C
warning_temp_time : 143
critical_comp_time : 0
Qué significa: warning_temp_time indica que el controlador pasó tiempo por encima del umbral de advertencia. El throttling suele parecerse a un “colapso por GC aleatorio.”
Decisión: Mejora el flujo de aire, disipadores, disposición del chasis. Si estás ajustando ZFS para compensar calor, estás escribiendo una política de ventiladores con pasos extra.
Task 13: Check NAND wear and spare blocks (a proxy for steady-state behavior)
cr0x@server:~$ sudo smartctl -a /dev/nvme0n1 | egrep 'Percentage Used|Data Units Written|Available Spare'
Available Spare: 100%
Available Spare Threshold: 10%
Percentage Used: 3%
Data Units Written: 18,442,118
Qué significa: Un “Percentage Used” alto no causa directamente el colapso por GC, pero las unidades gastadas pueden comportarse diferente, y poco spare puede reducir la reserva de rendimiento.
Decisión: Monitorea estos valores en el tiempo. Si ves inestabilidad de rendimiento y el desgaste es alto, reemplaza antes de estar depurando firmware en una noche de viernes.
Task 14: Measure sync latency directly (quick and dirty)
cr0x@server:~$ sudo zfs create -o sync=standard -o compression=off ssdpool/testsync
cr0x@server:~$ cd /ssdpool/testsync
cr0x@server:~$ /usr/bin/time -f "%e seconds" bash -c 'for i in {1..5000}; do dd if=/dev/zero of=f bs=4k count=1 conv=fsync oflag=dsync status=none; done'
12.84 seconds
Qué significa: Esto no es un conjunto de benchmarks; es una canaria. Si esto de repente se vuelve 60+ segundos bajo carga, tu camino sync está enfermo.
Decisión: Si la latencia sync es inaceptable, añade un SLOG correcto, reduce requisitos de sync en la aplicación (solo si es seguro), o mueve la carga.
Perillas de ajuste que importan (y las que mayormente no)
Mantén espacio libre como si importara
Los pools solo SSD son más felices con slack space. El asignador de ZFS necesita espacio, y el SSD necesita espacio.
Si tu pool está rutinariamente por encima del 80% y haces escrituras aleatorias, estás pidiendo un colapso por GC.
Los mirrors toleran esto mejor que RAIDZ, pero no te confíes.
Qué hacer:
- Apunta a 60–75% de utilización para cargas mixtas de escritura aleatoria que te importan.
- Usa discos más grandes de lo “necesario” y trata el extra como reserva de rendimiento.
- Considera overprovisioning explícito (dejar espacio sin particionar) si el firmware del SSD se beneficia de ello.
Autotrim: habilítalo, luego valida que no te perjudica
En stacks modernos, autotrim suele valer la pena para pools solo SSD. Ayuda a mantener el rendimiento steady-state informando al dispositivo qué está libre.
Pero TRIM no es gratis: añade IO y puede interactuar mal con ciertas unidades de consumo o firmware con bugs.
Postura operacional:
- Habilita autotrim y observa la latencia durante periodos de muchas eliminaciones.
- Si ves picos inducidos por TRIM, considera ventanas de trim programadas (donde esté soportado) o diferentes SSDs.
Recordsize/volblocksize: deja de fingir que un tamaño sirve para todo
El recordsize por defecto (128K) está bien para muchas cargas de archivos. Para bases de datos con páginas de 8K y actualizaciones frecuentes,
128K puede inflar la amplificación de escritura: ZFS reescribe registros lógicos más grandes y toca más metadata.
Guía con opinión:
- Bases de datos en datasets: prueba recordsize 16K (a veces 8K), compression lz4, atime off.
- Imágenes de VM en datasets: a menudo recordsize 64K o 128K está bien si el IO es algo secuencial; si es aleatorio, tamaños menores pueden ayudar.
- Imágenes de VM en zvols: establece volblocksize a 8K o 16K según IO invitado, y acepta que debes recrear para cambiar.
Compresión: LZ4 es el valor por defecto por una razón
La compresión reduce bytes escritos. Menos bytes escritos significa menos trabajo en la NAND, menos presión de GC y a veces mayor throughput.
En pools solo SSD, la compresión suele ser una característica de rendimiento disfrazada de ahorro de capacidad.
Usa compression=lz4 ampliamente salvo que tengas una razón medida para no hacerlo. Si la CPU es el cuello de botella, lo verás rápido.
Comportamiento sync: el ajuste más rápido y seguro es “standard”
Hay tres opciones comunes sobre las que la gente discute:
- sync=standard: ZFS respeta las solicitudes de la aplicación. Es el valor por defecto y normalmente correcto.
- sync=disabled: ZFS miente sobre sync. El rendimiento mejora. También lo hace tu perfil de riesgo.
- sync=always: fuerza sync para todo, suele usarse para pruebas o requisitos específicos de cumplimiento, y expondrá rápidamente SLOG/pool débiles.
Si ejecutas bases de datos, hipervisores o NFS para aplicaciones serias: no uses sync=disabled como “ajuste”.
Eso no es ajuste. Es cambiar el contrato de durabilidad. A veces es aceptable en entornos efímeros; en producción general, es cómo terminas explicando a finanzas lo que “acknowledged but not durable” significa.
SLOG: solo cuando lo necesitas, y solo si es el dispositivo correcto
Un SLOG ayuda a la latencia de escrituras sync cuando tu carga emite muchas escrituras sync y la latencia del pool principal es el cuello de botella.
No acelera escrituras async normales. No arregla un pool que está lleno y fragmentado. Y absolutamente debe ser seguro contra pérdida de energía.
Reglas de diseño de SLOG que sobreviven en la vida real:
- PLP es obligatorio para SLOG en cualquier entorno donde la pérdida de energía sea posible (es decir: todos).
- Espejarlo si perderlo sería operativamente doloroso; un SLOG perdido no provoca pérdida de datos principal, pero puede costar downtime y estrés.
- No lo sobredimensiones: necesitas suficiente para ráfagas; SLOGs enormes no te dan mucho más.
Ashift: ponlo bien en la creación o págalo para siempre
ashift establece el exponente del tamaño de sector del pool. En SSDs con sectores físicos de 4K, ashift=12 es típico.
Un ashift equivocado provoca ciclos read-modify-write y amplificación de escritura innecesaria. Es uno de esos errores que parece pequeño hasta que graficas la latencia.
Special vdevs: herramienta fantástica, aristas afiladas
Poner metadata y bloques pequeños en un special vdev puede reducir la amplificación de IO y mejorar la latencia. También puede convertirse en un punto único de fallo si no es redundante,
y puede calentarse si está mal dimensionado. La gente adora los special vdevs porque la curva de rendimiento sube inmediatamente. Los odian cuando toca reconstruir.
Si despliegas special vdevs:
- Espejalos. Trátalos como infraestructura crítica, porque lo son.
- Dimensiona para largo plazo, especialmente si vas a almacenar bloques pequeños allí.
- Monitorea su desgaste por separado. Pueden desgastarse más rápido que los dispositivos del pool principal.
A qué no obsesionarse primero
A la gente le encanta ajustar lo que puede cambiar rápido: tamaños de ARC, parámetros ocultos del módulo, planificadores de IO.
A veces importan. La mayoría de las veces, el colapso por GC es causado por presión de espacio, semánticas sync, desajuste de tamaños de bloque,
mezcla de clases de unidad o throttling térmico. Arregla las palancas grandes antes de hacer danza interpretativa con sysctls del kernel.
Broma #2: Si arreglas el colapso de GC cambiando doce sysctls, felicitaciones—has inventado un nuevo modo de outage con un nombre más elegante.
Tres micro-historias corporativas desde el campo
Incidente causado por una suposición errónea: “NVMe significa que sync es gratis”
Una empresa SaaS mediana migró de una mezcla HDD/SSD a un pool ZFS totalmente NVMe. El plan era simple:
“Discos más rápidos, misma arquitectura, todos ganan.” Su base de datos principal corría en ZVOLs presentados a VMs.
La migración fue un proyecto de fin de semana. En su mayoría funcionó. Luego llegó el lunes.
Picos de latencia aparecieron cada pocos minutos. Nodos de aplicación agotaban tiempo en transacciones. Los ingenieros vieron dispositivos NVMe con IOPS anunciadas enormes y asumieron
que la base de datos no podía estar esperando al almacenamiento. Buscaron problemas de red. Buscaron contención de locks. Escalaron pods de aplicación.
Empeoró, porque la base de datos realizó más reintentos, que generaron más escrituras sync.
La suposición errónea fue que la latencia NVMe es siempre baja, y que ZFS sobre NVMe se comporta como “NVMe puro pero con checksums”.
En realidad la carga era heavy en sync, el pool se estaba llenando rápido, y los NVMe de consumo tenían rendimiento sostenido aleatorio mediocre.
GC estaba realizando operaciones internas largas durante las ráfagas de flush de TXG.
La solución no fue exótica: mover el log de la base de datos a un SLOG con PLP, mantener la utilización del pool bajo control, y alinear volblocksize con el tamaño de página de la BD.
También mejoraron la refrigeración; las unidades estaban rozando el throttling durante picos de tráfico.
El incidente terminó no con un parche heroico al kernel, sino con una solicitud de compras y un plan de capacidad.
Optimización que salió mal: “Desactivemos sync para ganar velocidad”
Otra empresa tenía un farm de build corriendo en exportaciones NFS desde un pool ZFS solo SSD. Los tiempos de build importaban, y los desarrolladores se quejaban ruidosamente.
Alguien notó que los clientes NFS hacían muchas escrituras sync. Otro vio un post sugiriendo sync=disabled.
Puedes imaginar lo que pasó después.
Los tiempos de build mejoraron inmediatamente. Los gráficos se veían geniales. El equipo declaró la victoria y siguió adelante.
Semanas después, un evento de energía afectó un rack. La cobertura de UPS fue parcial y el nodo de almacenamiento reinició. El pool importó limpio.
El filesystem se montó. Todos se relajaron. Luego la caché de build empezó a devolver artefactos corruptos de forma sutil: objetos equivocados, checksums desajustados,
fallos de tests intermitentes que parecían código inestable.
El postmortem fue incómodo porque nada “se cayó” de forma obvia. El sistema hizo exactamente lo que se le dijo:
reconocer escrituras sin esperar almacenamiento estable. Eso es lo que significa sync=disabled. Habían cambiado durabilidad por velocidad sin documentarlo,
y sin delimitar el radio de impacto.
La solución fue aburrida y algo humillante: restaurar sync=standard, añadir un SLOG correcto dimensionado para la carga sync,
y ajustar las opciones de exportación/montaje NFS para reducir semánticas sync innecesarias en las rutas de caché que podían tolerarlo.
También añadieron una política: cambios de rendimiento que alteren durabilidad requieren aprobación y plan de rollback.
Práctica correcta y aburrida que salvó el día: “Mantuvimos 30% libre y vigilamos el desgaste”
Un equipo de servicios financieros corría una plataforma de analítica sobre ZFS con mirrors SSD. La carga era fea: ráfagas de ingest, compaction intensa,
muchas eliminaciones y reconstrucción periódica de datasets derivados. Era el tipo de perfil IO que convierte diseños optimistas en generadores de incidentes.
Su arma secreta no era un truco ingenioso. Era disciplina.
Tenían una regla estricta: no superar un umbral de utilización en el pool. Cuando negocio pedía “un poco más”, compraban capacidad.
También monitorizaban temperatura de unidades, indicadores de desgaste y comportamiento de trim semanal. No por amor a los dashboards, sino porque les permitía detectar
“esto se está poniendo peor” antes de que se convirtiera en “esto está caído”.
Un trimestre el volumen de ingest aumentó. La latencia empezó a subir en jobs nocturnos. El on-call lo vio temprano: aumentó la fragmentación, los trims tardaron más,
y el espacio libre del pool bajó por debajo de su línea de confort. Ampliaron el pool y re-balancearon cargas antes de la siguiente semana de ingest.
Sin outage. Sin drama. Solo un ticket cerrado con la nota: “Capacidad de reserva restaurada.”
La lección es desagradablemente poco sexy: la mejor mitigación contra el colapso por GC es no ir apretado en espacio, más monitorización que te diga cuando te acercas al precipicio.
La mayoría de las victorias de confiabilidad parecen “nada ocurrió”, por eso la gente intenta reemplazarlas con excitación.
Errores comunes: síntomas → causa raíz → solución
1) Síntoma: stalls periódicos de varios segundos, especialmente en commits de base de datos
- Causa raíz: carga heavy en sync golpeando dispositivos del pool (sin SLOG), agravada por GC del SSD durante flushes de TXG.
- Solución: añadir SLOG con PLP (y mirror si es necesario), mantener la utilización baja, confirmar refrigeración, ajustar recordsize/volblocksize para actualizaciones más pequeñas.
2) Síntoma: rendimiento bueno tras despliegue, luego degrada lentamente en semanas
- Causa raíz: sin TRIM/autotrim, churn alto de eliminaciones, SSDs pierden conocimiento de páginas libres y alcanzan amplificación de escritura steady-state.
- Solución: habilitar autotrim, validar soporte de discard, considerar trims programados, dejar más espacio libre/OP, considerar otra clase de unidad.
3) Síntoma: colapso de IOPS de escritura aleatoria cuando el pool alcanza ~85–90% de uso
- Causa raíz: asignador y FTL se quedan sin espacio limpio; la fragmentación aumenta; GC tiene menos opciones.
- Solución: ampliar capacidad, migrar datos fríos, aplicar cuotas/reservas, establecer umbral de política y alerta antes de que sea crítico.
4) Síntoma: un dispositivo NVMe muestra peor rendimiento, el pool parece “irregular”
- Causa raíz: throttling térmico, diferencias de firmware, problemas de enlace PCIe, o una unidad entrando en un régimen diferente de GC.
- Solución: revisar SMART de NVMe para temperaturas, verificar velocidad/ancho de enlace PCIe, actualizar firmware en ventana controlada, mejorar flujo de aire.
5) Síntoma: el ajuste “funcionó”, luego un reinicio lo empeoró
- Causa raíz: cambios aplicados ad hoc (params de módulos, sysctls) sin gestión de configuración; el rollback no fue real.
- Solución: codificar ajustes, usar control de cambios, registrar métricas base y probar bajo carga con las mismas versiones de kernel/módulos.
6) Síntoma: special vdev se desgasta rápidamente y se vuelve cuello de botella
- Causa raíz: special_small_blocks configurado demasiado alto, special vdev demasiado pequeño, hot set de metadata/bloques pequeños concentrado allí.
- Solución: espejar y dimensionar apropiadamente el special vdev, reconsiderar el umbral special_small_blocks, monitorear desgaste y ancho de banda por separado.
7) Síntoma: “Desactivamos sync y es rápido”, seguido de corrupción misteriosa tras pérdida de energía
- Causa raíz: contrato de durabilidad cambiado; las escrituras reconocidas no eran realmente durables.
- Solución: revertir a sync=standard, usar SLOG para aceleración sync legítima, y documentar dónde la durabilidad asíncrona es aceptable (si existe).
Listas de verificación / plan paso a paso
Paso a paso: estabilizar un pool ZFS solo SSD que muestra síntomas de colapso por GC
- Captura evidencia durante el pico. Ejecuta
zpool iostat -v 1, revisa temperaturas NVMe y anota utilización y frag del pool. - Confirma presión sync. Revisa actividad del ZIL y si la carga es realmente heavy en sync (commits BD, semánticas NFS, ajustes de hipervisor).
- Revisa TRIM/autotrim. Habilita autotrim si está off, valida soporte de discard y observa latencia por TRIM en periodos de borrados intensos.
- Pon la utilización bajo control. Libera espacio, amplia o mueve datos. Si estás por encima de ~80% con escrituras aleatorias, trata eso como el bug principal.
- Arregla lo básico de térmica y firmware. El throttling NVMe parece “cambios de humor” del almacenamiento. No lo ignores.
- Dimensiona correctamente bloques. Ajusta recordsize de datasets (o recrea zvol con volblocksize correcto). No copies ciegamente 128K en todos lados.
- Usa compresión. Prefiere lz4 salvo que hayas medido lo contrario.
- Si necesitas performance sync, añade un SLOG correcto. PLP, idealmente espejado. Luego mide de nuevo.
- Vuelve a probar bajo carga realista. Un benchmark secuencial sintético no es evidencia para una carga de base de datos.
- Configura alertas. CAP %, FRAG %, temperaturas NVMe, latencia de escritura y contadores de throttle de ZFS. Es más fácil prevenir que solucionar heroicamente.
Checklist de diseño: diseñar un pool ZFS solo SSD que no te avergüence
- Topología: Mirrors para cargas con latencia e IOPS; ten precaución con RAIDZ ancho para escrituras aleatorias.
- Unidades: Prefiere SSD empresariales con steady-state predecible y PLP donde sea necesario.
- Reserva de espacio: Planifica capacidad para mantener espacio libre saludable sin mendigar presupuesto cada mes.
- ashift: Confirma alineación 4K y ashift correcto al crear el pool.
- autotrim: Habilita y valida con el modelo/firmware de tus unidades.
- Mapeo de cargas: Ajusta recordsize/volblocksize por dataset/zvol, no por la “vibra” del pool.
- Monitorización: Percentiles de latencia, temperaturas, indicadores de desgaste, contadores de throttle de ZFS y tendencias de utilización.
- Control de cambios: Cualquier cambio que altere durabilidad (sync, cache de escritura) necesita revisión y plan de rollback.
Preguntas frecuentes
1) ¿Es el colapso por garbage collection un problema de “mal SSD” o de “mal ZFS”?
Normalmente ni uno ni otro. Es un problema de interacción: un sistema de archivos CoW más el firmware del SSD bajo presión de espacio y escrituras por ráfagas.
Mejores SSDs (más OP, mejor firmware) amplían el área segura de operación. Mejor diseño ZFS (reserva de espacio, tamaños de bloque correctos, ruta sync sensata) previene el precipicio.
2) ¿Debo siempre habilitar autotrim en pools solo SSD?
En la mayoría de entornos modernos, sí. Luego verifica soporte de discard y observa cualquier regresión bajo churn de borrados.
Si tu modelo de unidad se comporta mal con TRIM continuo, podrías necesitar trim programado o hardware distinto.
3) ¿Agregar más RAM (ARC) arregla stalls de SSD?
Puede enmascararlos al absorber ráfagas, pero no cambia el comportamiento steady-state del SSD.
Si el pool no puede vaciar rápido porque el dispositivo stall, eventualmente volverás a encontrar throttling.
4) ¿Cuándo vale la pena un SLOG?
Cuando tienes un cuello de botella medible de escrituras sync: bases de datos que hacen commits frecuentemente, NFS con semánticas sync, cargas de VM con muchos fsync.
Un SLOG no es caché general de escritura. Y sin PLP no es una optimización; es un bug de fiabilidad.
5) ¿Puedo cambiar volblocksize después de crear un zvol?
No. Debes recrear el zvol y migrar datos. Planifica volblocksize por adelantado y apúntalo para que el futuro tú no lo “optimice” hasta el caos.
6) ¿La compresión realmente ayuda en NVMe rápido?
A menudo sí. Menos datos escritos significan menos trabajo en NAND y menos presión de GC. LZ4 suele ser lo bastante económico como para mejorar throughput.
Mide para tu carga, especialmente si estás limitado por CPU o almacenando datos incomprimibles.
7) ¿Por qué empeora todo conforme el pool se llena aunque el SSD tenga capacidad sobrante?
Porque “capacidad sobrante” en SSD no es lo mismo que “espacio libre” en ZFS. ZFS necesita metaslabs libres; el FTL del SSD necesita bloques libres.
Alta utilización y fragmentación reducen la flexibilidad para ambas capas.
8) ¿RAIDZ es malo para pools SSD?
No universalmente, pero es menos tolerante para escrituras aleatorias pequeñas y cargas sync-heavy. Los mirrors suelen ofrecer mejor latencia y consistencia de IOPS.
Si tu carga es mayormente secuencial y la capacidad importa, RAIDZ puede estar bien—si mantienes la utilización sensata.
9) ¿sync=disabled es aceptable alguna vez?
A veces, para datos realmente efímeros donde aceptas explícitamente perder escrituras recientes ante un crash o pérdida de energía.
Debe ser una decisión política consciente, documentada y acotada, no un “tweak” de rendimiento.
10) ¿Cómo sé si mi problema es GC o simplemente throttling térmico?
Revisa logs SMART de NVMe para temperatura y warning time, correlaciona picos con calor y verifica comportamiento consistente del enlace PCIe.
El throttling térmico suele producir degradación repetible “después de X minutos de carga” que se recupera al enfriarse.
Conclusión: siguientes pasos prácticos
El colapso por GC en pools ZFS solo SSD es prevenible. No con magia. Con fundamentos:
mantén espacio libre, empareja tamaños de bloque a las cargas, respeta las semánticas sync y no escatimes en los dispositivos que definen la durabilidad.
Siguientes pasos que rinden frutos esta semana:
- Habilitar y verificar autotrim (y confirmar soporte de discard).
- Establecer una política de utilización y alertar antes de cruzarla.
- Auditar recordsize/volblocksize por dataset/zvol importante.
- Si eres heavy en sync, implementar un SLOG con PLP y medir de nuevo.
- Revisar temperaturas NVMe bajo carga pico y arreglar flujo de aire antes de “afinar”.
- Escribir tu contrato de durabilidad (ajustes sync, expectativas de la aplicación) para que el trabajo de rendimiento no se convierta en una apuesta por pérdida de datos.
El almacenamiento es la parte del sistema que recuerda tus errores. Ajústalo como si fuera a testificar luego.