Cuidado al activar "userobj_accounting" en ZFS
Tras escribir Actualización de características en un sistema de ficheros ZFS (20200528) y decidir que todas mis máquinas con ZFS eran estables y no necesitaban la seguridad de poder volver a una versión previa del Sistema Operativo, me puse a actualizar mis ZPOOLs ZFS con todas las características opcionales disponibles.
La actualización fue simple, rápida e indolora salvo en uno de mis servidores. En ese servidor la operación se eternizó, con una carga de CPU muy elevada, a pesar de que la actividad de disco era relativamente baja.
De todas las características opcionales activadas, la única potencialmente costosa es userobj_accounting, porque requiere repasar los metadatos de todos los archivos y directorios en el ZPOOL.
La extrema carga de CPU en ese servidor es evidente:
top - 02:11:43 up 16 min, 1 user, load average: 69.54, 61.05, 34.18 Tasks: 298 total, 66 running, 171 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 99.5 sy, 0.0 ni, 0.0 id, 0.3 wa, 0.0 hi, 0.2 si, 0.0 st KiB Mem : 1994684 total, 240660 free, 1568512 used, 185512 buff/cache KiB Swap: 0 total, 0 free, 0 used. 279708 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 18438 root 20 0 0 0 0 R 3.6 0.0 0:00.74 arc_prune 18603 root 20 0 0 0 0 R 3.6 0.0 0:00.15 arc_prune 18630 root 20 0 0 0 0 R 3.3 0.0 0:18.67 arc_prune 18461 root 20 0 0 0 0 R 3.3 0.0 0:00.63 arc_prune 18483 root 20 0 0 0 0 R 3.3 0.0 0:00.58 arc_prune 18507 root 20 0 0 0 0 R 3.3 0.0 0:00.42 arc_prune 18522 root 20 0 0 0 0 R 3.3 0.0 0:00.37 arc_prune 18528 root 20 0 0 0 0 R 3.3 0.0 0:00.36 arc_prune 18540 root 20 0 0 0 0 R 3.3 0.0 0:00.31 arc_prune 18548 root 20 0 0 0 0 R 3.3 0.0 0:00.28 arc_prune 18552 root 20 0 0 0 0 R 3.3 0.0 0:00.26 arc_prune 18553 root 20 0 0 0 0 R 3.3 0.0 0:00.26 arc_prune 18560 root 20 0 0 0 0 R 3.3 0.0 0:00.25 arc_prune 18563 root 20 0 0 0 0 R 3.3 0.0 0:00.24 arc_prune 18581 root 20 0 0 0 0 R 3.3 0.0 0:00.16 arc_prune 18589 root 20 0 0 0 0 R 3.3 0.0 0:00.15 arc_prune 18596 root 20 0 0 0 0 R 3.3 0.0 0:00.15 arc_prune 18599 root 20 0 0 0 0 R 3.3 0.0 0:00.15 arc_prune
Aquí vemos que la carga de CPU es muy elevada, casi 70, con más de un 99% del tiempo consumido en el Sistema Operativo por hilos ejecutando arc_prune. Buscando ese nombre en internet, aparece un enlace interesante: arc_prune: high load and soft lockups #6223.
Resumiendo: este servidor solo tiene dos gigabytes de RAM y revisar el ZPOOL para actualizar la información proporcionada con la característica opcional userobj_accounting necesita más memoria de trabajo.
Confirmemos que es el caso:
root@csi:~# cat /proc/spl/kstat/zfs/arcstats |grep -i dnode dnode_size 4 409194768 arc_dnode_limit 4 76595865
Efectivamente, el uso de dnode_size supera con creces el valor de arc_dnode_limit. Los hilos arc_prune en el Sistema Operativo están esforzándose al máximo para mantener dnode_size bajo control. Eso consume casi toda la capacidad de la máquina, por lo que el progreso es penosamente lento.
Por defecto, esta caché está limitada al 10% de la RAM, pero podemos subirlo bastante (no demasiado, ya que pondría en peligro la estabilidad del Sistema Operativo). Veamos:
root@csi:~# cat /sys/module/zfs/parameters/zfs_arc_dnode_limit_percent 10 root@csi:~# echo 80 >/sys/module/zfs/parameters/zfs_arc_dnode_limit_percent root@csi:~# cat /sys/module/zfs/parameters/zfs_arc_dnode_limit_percent 80
Ahora permitimos que esa caché ocupe hasta el 80% de la RAM.
A medida que la revisión del ZPOOL progresa, vamos viendo cómo va la cosa:
root@csi:~# cat /proc/spl/kstat/zfs/arcstats |i grep -i dnode dnode_size 4 421004864 arc_dnode_limit 4 612766924 [..] root@csi:~# cat /proc/spl/kstat/zfs/arcstats | grep -i dnode dnode_size 4 424327328 arc_dnode_limit 4 612766924
Aquí vemos que ya no hay problemas con esta caché, pero el progreso sigue siendo desesperante. Veamos otras cachés ZFS:
root@csi:~# cat /proc/spl/kstat/zfs/arcstats |grep -i arc_meta arc_meta_used 4 1110475504 arc_meta_limit 4 765958656 arc_meta_max 4 1110984472 arc_meta_min 4 16777216
Aquí estamos tropezándonos con el límite de otra caché. Intentemos subir su límite:
root@csi:~# cat /sys/module/zfs/parameters/zfs_arc_meta_limit_percent 75 root@csi:~# echo 90 >/sys/module/zfs/parameters/zfs_arc_meta_limit_percent
Aquí subimos el límite del tamaño de esta caché del 70% al 90% de la RAM.
La máquina colapsa. No tenemos suficiente RAM.
Segundo intento
Por suerte, los ZPOOLs en este servidor en concreto no se importan por defecto al reiniciar la máquina. Los importo manualmente cuando los necesito.
Revisando la documentación, podemos leer lo siguiente:
userobj_accounting GUID org.zfsonlinux:userobj_accounting READ-ONLY COMPATIBLE yes DEPENDENCIES extensible_dataset This feature allows administrators to account the object usage information by user and group. This feature becomes active as soon as it is enabled and will never return to being enabled. Each filesystem will be upgraded automatically when remounted, or when new files are created under that filesystem. The upgrade can also be started manually on filesystems by running `zfs set version=current <pool/fs>`. The upgrade process runs in the background and may take a while to complete for filesystems containing a large number of files.
En este texto hay un par de detalles de interés:
- La contabilidad de userobj_accounting se realiza dataset a dataset. Si pudiéramos actualizar los datasets uno por uno, evitamos la competencia de recursos entre ellos.
- Se puede actualizar un dataset dado montándolo o bien ejecutando el comando zfs set version=current <pool/fs>.
Los pasos, por lo tanto, resultan simples: Importamos los ZPOOL sin montar sus datasets y vamos realizando la contabilidad userobj_accounting uno a uno:
root@csi:~# zpool import -N blue root@csi:~# zfs mount blue/blue_1 [.. esperamos a que la contabilidad termine ..] root@csi:~# zfs mount blue/blue_2 [.. esperamos a que la contabilidad termine ..] root@csi:~# zfs mount blue/blue_3 [.. esperamos a que la contabilidad termine ..] root@csi:~# zfs mount blue/blue_4 [.. esperamos a que la contabilidad termine ..]
Cada uno de estos datasets contiene un cuarto de millón de ficheros. Podemos ir viendo cómo va el progreso en tiempo real:
root@csi:~# cat /proc/spl/kstat/zfs/arcstats | grep -e "arc_meta\|dnode" dnode_size 4 33766512 arc_meta_used 4 116807216 arc_meta_limit 4 765958656 arc_dnode_limit 4 76595865 arc_meta_max 4 122911392 arc_meta_min 4 16777216
Aquí vemos que la máquina va cómoda. Examinemos la actividad de disco:
root@csi:~# zpool iostat -v 1 capacity operations bandwidth pool alloc free read write read write ------------ ----- ----- ----- ----- ----- ----- blue 3.51T 116G 22 100 144K 695K disco_blue 3.51T 116G 22 100 144K 695K ------------ ----- ----- ----- ----- ----- -----
Veamos cómo avanza la contabilidad de userobj_accounting:
root@csi:~# zfs groupspace blue TYPE NAME USED QUOTA OBJUSED OBJQUOTA POSIX Group root 2.24M none 467 none POSIX Group www-data 512B none 1 none root@csi:~# zfs groupspace blue/blue_1 TYPE NAME USED QUOTA OBJUSED OBJQUOTA POSIX Group www-data 1000G none - -
Terminamos con el ZPOOL blue. Sin trashing, el proceso es bastante rápido (minutos). Perfecto.
Ahora nos queda hacer lo mismo con el ZPOOL datos. Los pasos son los mismos, pero aquí tenemos datasets con más ficheros y tendremos que subir los parámetros de las diferentes cachés como se indicó más arriba. Podemos ver si tenemos problemas con el comando iostat, por ejemplo, si tenemos muchas lecturas de disco pero pocas escrituras (o pocas lecturas pero sin escrituras). Estos son síntomas de trashing de las cachés.
Conclusiones
-
Cuando se activa la característica opcional userobj_accounting en un ZPOOL ZFS, las necesidades de RAM durante el proceso de actualización son proporcionales al número de ficheros en el ZPOOL. En general, las necesidades son modestas, pero en mi caso tengo decenas de millones de ficheros en una máquina con solo dos gigabytes de RAM.
-
En caso necesario, se puede migrar dataset a dataset. En ese caso, la necesidad de RAM es proporcional al número de ficheros en el dataset con más ficheros.
No obstante si el ZPOOL forma parte del arranque del Sistema Operativo, en general no podremos importar el ZPOOL sin montar los datasets a menos que arranquemos la máquina en modo rescate o similar.
-
En una situación de emergencia, configurar las cachés ZFS de forma más agresiva puede salvar el día. Tras el proceso podemos volver a dejarlas como estaban o mejor, si es posible, reiniciamos la máquina para asegurarnos de que todo va bien y la configuración vuelve a un estado "normal".
-
En todo caso, todo esto solo es problema al activar esta característica opcional. Una vez que se completa la contabilidad de fondo, el coste de mantener los metadatos userobj_accounting actualizados es despreciable, por muchos ficheros que tengamos.