¿AIO fsync podría mejorar el rendimiento de dpkg?

¿AIO fsync podría mejorar el rendimiento de dpkg?

¿Podría dpkg, el administrador de paquetes de Debian, obtener una mejora notable en el rendimiento utilizando una de las operaciones AIO fsync(), en lugar de sync_file_range() + fsync()?

La API fsync2() [propuesta] es esencialmente idéntica a la API AIO_FSYNC/AIO_FDSYNC existente, excepto que es sincrónica y eso es lo que las aplicaciones quieren evitar.

El único argumento que me han presentado en contra de [usar] AIO_FSYNC es que "la implementación es solo una cola de trabajo", lo cual en gran medida no tiene sentido porque es independiente de la implementación del sistema de archivos pero permite la paralelización automática del lado del kernel de todas las operaciones fsync emitidas. Esto permite que los sistemas de archivos optimicen automáticamente las escrituras de diario innecesarias al completar operaciones fsync simultáneas: XFS, ext4, etc. ya hacen esto cuando las aplicaciones de usuario ejecutan fsync() simultáneamente desde muchos procesos/hilos...

Esta implementación simple permite una carga de trabajo simple "untar con aio fsync" (es decir, "escribir muchos archivos de 4 kB y aio_fsync() en lotes a medida que avanzamos, retirando fsync()s completados antes de enviar un nuevo lote") en XFS para pasar de alrededor de 2000 archivos/s (latencia de E/S de escritura síncrona vinculada) a más de 40 000 archivos/s (escritura de iops vinculadas al almacenamiento back-end).

--David Chinner

La carga de trabajo de ejemplo tiene similitudes con apt-get installo dpkg -i(dependiendo en parte del tamaño de los archivos en los paquetes instalados :-). dpkgdebe fsync() efectivamente todos los archivos descomprimidos, antes de cambiarles el nombre.

dpkgha sido optimizado siguiendo los consejos de Ted T'so. La optimización consiste en agregar llamadas a sync_file_range() en ciertos puntos. Esta llamada al sistema nonoproporciona las mismas garantías que fsync(). Por favor lea la documentación pararango_archivo_sincronizado()y observe la advertencia destacada :-).

Ninguna de estas operaciones escribe los metadatos del archivo. Por lo tanto, a menos que la aplicación realice estrictamente sobrescrituras de bloques de disco ya creados, no hay garantías de que los datos estén disponibles después de una falla.

dpkgactiva la reescritura de datos inmediatamente después de escribir cada archivo, utilizando SYNC_FILE_RANGE_WRITE. Primero escribe todos los archivos del paquete. Luego hay un segundo paso por los archivos, que espera la reescritura de datos usando SYNC_FILE_RANGE_WAIT_BEFORE, llama fsync()y finalmente cambia el nombre del archivo a su lugar.

Ver confirmaciones:

Mi hipótesis es que, en cambio, paralelizar las operaciones fsync() podría mejorar el rendimiento, al permitir un procesamiento por lotes más eficiente delmetadatosescribe, particularmente agrupando las barreras asociadas/vaciados de caché de disco que son necesarios para garantizar que los metadatos en el disco sean consistentes en todo momento.

EDITAR: Parece que mi hipótesis era demasiado simple, al menos cuando uso el sistema de archivos ext4:

La segunda serie de llamadas sync_file_range(), con la operación SYNC_FILE_RANGE_WAIT_BEFORE, se bloqueará hasta que se complete la reescritura iniciada anteriormente. Básicamente, esto garantiza que la asignación retrasada se haya resuelto; es decir, los bloques de datos han sido asignados y escritos, y el inodo actualizado (en la memoria), pero no necesariamente enviado al disco.

La llamada [fsync()] en realidad forzará el inodo al disco. En el caso del sistema de archivos ext4, el primer [fsync()] en realidad enviará todos los inodos al disco., y todas las llamadas [fsync()] posteriores son, de hecho, no operativas (suponiendo que los archivos 'a', 'b' y 'c' estén todos en el mismo sistema de archivos). Pero lo que significa es que minimiza el número de confirmaciones (pesadas) de jbd2 al mínimo.

Utiliza una llamada al sistema específica de Linux --- sync_file_range() --- pero el resultado debería ser un rendimiento más rápido en todos los ámbitos para todos los sistemas de archivos. Así que no considero que esto sea un truco específico para ext4, aunque probablemente haga las cosas más rápidas para ext4 que cualquier otro sistema de archivos.

--Ted T'so

Podría ser que algún otro sistema de archivos se beneficiaría utilizando las operaciones AIO fsync() en su lugar.

bcachefs(en desarrollo) pretende aislar IO entre diferentes archivos mucho mejor que ext4. Entonces podría ser particularmente interesante probarlo.

Parece que ext4 podría no estar tan bien optimizado para un patrón AIO fsync() puro (supongo que otros sistemas de archivos también podrían tener la misma restricción). Si es así, supongo que sería posible hacer todas las mismas llamadas sync_file_range() primero, luego comenzar todas las operaciones AIO fsync() como una segunda ronda y terminar cambiando el nombre de todos los archivos a fsync() operaciones completadas.


VIEJO:

El primer paso en una investigación de este tipo debería ser la medición :-).

Es posible deshabilitar la parte fsync(), usando echo "force-unsafe-io" > /etc/dpkg/dpkg.cfg.d/force-unsafe-io.

Hasta ahora, intenté ejecutarlo apt-get installen strace -f -wc, en un contenedor de Debian 9. Por ejemplo, al instalar el aptitudepaquete usando "io inseguro", solo hay 495 llamadas fsync() sincrónicas. Mientras se instala aptitudenormalmente, hay llamadas 1011 fsync(). "unsafe io" también deshabilitó la SYNC_FILE_RANGE_WAIT_BEFOREllamada, reduciendo la cantidad de llamadas sync_file_range() de 1036 a 518.

Sin embargo, estaba mucho menos claro si esto reducía el tiempo medio necesario. Si así fue, no parece ser más que la variación aleatoria entre ejecuciones. Hasta ahora, probé esto en ext4 y XFS, en un disco duro mecánico.


apt-getdice que el tamaño total de los 518 archivos descomprimidos fue de 21,7 MB (consulte el resultado a continuación).

Con respecto a las llamadas 495 fsync(), que permanecieron presentes incluso cuando se solicitó "io inseguro":

En ext4, la salida de strace mostró que el tiempo dedicado a las llamadas fsync() restantes fue de aproximadamente 11 segundos. En XFS, la cifra correspondiente fue de unos 7 segundos. En todos los casos, esta fue la mayor parte del tiempo necesario para la instalación aptitude.

Entonces, incluso si "io inseguro" ofrece una pequeña mejora en la instalación aptitude, parece que necesitaría /varestar montado en un dispositivo significativamente más rápido (latencia más baja) que el resto del sistema, antes de que la diferencia sea realmente notable. Pero no estoy interesado en optimizar ese caso de nicho.

La ejecución a continuación strace -f -y -e trace=fsync,renamemostró que para las llamadas fsync() restantes, 2 de ellas estaban activadas /etc/ld.so.cache~y 493 de ellas eran archivos dentro, /var/lib/dpkg/es decir, la base de datos del paquete.

318 de las llamadas a fsync() están bajo /var/lib/dpkg/updates/. Estos son incrementos de la base de datos dpkg /var/lib/dpkg/status. Los incrementos se acumulan en la base de datos principal ("puntos de control") al final de la ejecución de dpkg.


The following NEW packages will be installed:
  aptitude aptitude-common libboost-filesystem1.62.0 libboost-iostreams1.62.0 libboost-system1.62.0 libcgi-fast-perl libcgi-pm-perl
  libclass-accessor-perl libcwidget3v5 libencode-locale-perl libfcgi-perl libhtml-parser-perl libhtml-tagset-perl libhttp-date-perl
  libhttp-message-perl libio-html-perl libio-string-perl liblwp-mediatypes-perl libparse-debianchangelog-perl libsigc++-2.0-0v5 libsqlite3-0
  libsub-name-perl libtimedate-perl liburi-perl libxapian30
0 upgraded, 25 newly installed, 0 to remove and 0 not upgraded.
Need to get 0 B/6000 kB of archives.
After this operation, 21.7 MB of additional disk space will be used.

Respuesta1

La pregunta sugiere que esto no ayudará en ext4 o XFS.

También probé instalando un paquete mucho más grande ( linux-image-4.9.0-9-amd64). Todavía parecía tomar el mismo tiempo, independientemente de --force-unsafe-io.

ext2

En ext2, --force-unsafe-iose redujo el tiempo de instalación linux-imagede 50 segundos a 13 segundos.

El núcleo en el que ejecuté las pruebas fue 5.0.17-200.fc29.x86_64, que utiliza CONFIG_EXT4_USE_FOR_EXT2.

Probé ext2 usando la implementación del espacio de usuario aio_fsync(). Sin embargo, la mejor mejora no dependió del uso de AIO fsync().

En realidad, mi mejora se debió a un efecto secundario. Cambié dpkg para realizar todas las operaciones fsync() primero y luego todas las operaciones rename(). Mientras que el dpkg sin parches llamó rename() después de cada fsync(). Utilicé profundidades de cola AIO de hasta 256. AIO fsync() con una profundidad de cola de 1 era significativamente más lento que fsync() sincrónico; parece que hubo algo de sobrecarga. La mejor mejora también requería realizar SYNC_FILE_RANGE_WRITEprimero todas las operaciones originales. La versión mejorada se instaló linux-imageen unos 18 segundos.

Este orden de operaciones es en realidad lo que Ted T'so sugirió originalmente :-D. Lo que pasa es que enCONFIG_EXT4_USE_FOR_EXT2 , fsync() también sincroniza de manera útil el directorio principal. Primero desea realizar toda la manipulación del nombre del archivo, para evitar múltiples actualizaciones en el disco para cada directorio. Creo que esto no sucede con la CONFIG_EXT2implementación anterior ni con un ext4sistema de archivos normal.

ext4: haga que fsync sincronice el directorio principal sin diario de verdad esta vez

[...] Esto también incluye el modo predeterminado ext2, obviamente. [...]

https://elixir.bootlin.com/linux/v5.0.17/source/fs/ext4/fsync.c#L38

 * If we're not journaling and this is a just-created file, we have to
 * sync our parent directory (if it was freshly created) since
 * otherwise it will only be written by writeback, leaving a huge
 * window during which a crash may lose the file.  This may apply for
 * the parent directory's parent as well, and so on recursively, if
 * they are also freshly created.

Como antes, reemplazar la etapa fsync() con sync() parece dar un rendimiento inquietantemente bueno, coincidente --force-unsafe-io:-). sync() o syncfs() parecen ser muy buenos si puedes usarlos.

btrfs

Cuando comencé a probar aio_fsync() en btrfs, descubrí que las operaciones fsync() pueden causar que el cambio de nombre() del archivo se bloquee, debido a una corrección reciente de la integridad de los datos. Decidí que no estoy interesado en btrfs.

¿Por qué rename() tarda más cuando se llama primero a fsync()?

información relacionada