Könnte AIO fsync die Dpkg-Leistung verbessern?

Könnte AIO fsync die Dpkg-Leistung verbessern?

Könnte dpkg, der Debian-Paketmanager, eine spürbare Leistungssteigerung erzielen, wenn er eine der AIO-fsync()-Operationen anstelle von sync_file_range() + fsync() verwendet?

Die [vorgeschlagene] fsync2()-API ist im Wesentlichen identisch mit der vorhandenen AIO_FSYNC/AIO_FDSYNC-API, außer dass sie synchron ist und dies ist, was Anwendungen vermeiden möchten.

Das einzige Argument, das mir gegen [die Verwendung] von AIO_FSYNC vorgebracht wurde, ist, dass „die Implementierung nur eine Arbeitswarteschlange ist“, was weitgehend unsinnig ist, da es unabhängig von der Dateisystemimplementierung ist, aber eine automatische Parallelisierung aller ausgegebenen Fsync-Operationen auf der Kernelseite ermöglicht. Dadurch können die Dateisysteme dann automatisch unnötige Journalschreibvorgänge optimieren, wenn gleichzeitige Fsync-Operationen abgeschlossen werden – XFS, ext4 usw. tun dies bereits, wenn Benutzeranwendungen fsync() gleichzeitig von vielen Prozessen/Threads aus ausführen …

Diese einfache Implementierung ermöglicht eine einfache „untar mit aio fsync“-Arbeitslast (d. h. „schreiben Sie viele 4-kB-Dateien und aio_fsync() in Stapeln, während wir vorgehen, und löschen Sie abgeschlossene fsync()s, bevor wir einen neuen Stapel versenden“) auf XFS, die von etwa 2000 Dateien/s (gebundene synchrone Schreib-IO-Latenz) auf über 40.000 Dateien/s (gebundene Schreib-IOps im Back-End-Speicher) gesteigert werden kann.

--Dave Chinner

Die Beispiel-Arbeitslast weist Ähnlichkeiten mit apt-get installoder dpkg -iauf (teilweise abhängig von der Größe der Dateien in den installierten Paketen :-). dpkgmuss alle entpackten Dateien effektiv mit fsync() ausführen, bevor es sie an Ort und Stelle umbenennt.

dpkgwurde mit Hilfe von Ted T's Rat optimiert. Die Optimierung besteht darin, an bestimmten Stellen Aufrufe von sync_file_range() hinzuzufügen. Dieser Systemaufrufnichtbieten die gleichen Garantien wie fsync(). Bitte lesen Sie die Dokumentation fürsync_file_range()und beachten Sie die deutliche Warnung :-).

Bei keinem dieser Vorgänge werden die Metadaten der Datei ausgelesen. Sofern die Anwendung nicht ausschließlich das Überschreiben bereits instanziierter Datenträgerblöcke durchführt, besteht daher keine Garantie dafür, dass die Daten nach einem Absturz verfügbar sind.

dpkglöst das Rückschreiben der Daten unmittelbar nach dem Schreiben jeder Datei mit aus SYNC_FILE_RANGE_WRITE. Dabei werden zunächst alle Dateien für das Paket geschrieben. Anschließend erfolgt ein zweiter Durchlauf durch die Dateien, der auf das Rückschreiben der Daten mit wartet SYNC_FILE_RANGE_WAIT_BEFORE, aufruft fsync()und die Datei schließlich an der richtigen Stelle umbenennt.

Siehe Commits:

Meine Hypothese ist, dass die Parallelisierung der fsync()-Operationen die Leistung verbessern könnte, indem sie eine effizientere Stapelverarbeitung derMetadatenSchreibvorgänge, insbesondere die Stapelverarbeitung der zugehörigen Barrieren/Festplatten-Cache-Flushs, die erforderlich sind, um sicherzustellen, dass die Metadaten auf der Festplatte jederzeit konsistent sind.

BEARBEITEN: Es scheint, dass meine Hypothese zu einfach war, zumindest bei Verwendung des ext4-Dateisystems:

Die zweite Reihe von sync_file_range()-Aufrufen mit der Operation SYNC_FILE_RANGE_WAIT_BEFOREwird blockiert, bis der zuvor eingeleitete Writeback abgeschlossen ist. Dadurch wird grundsätzlich sichergestellt, dass die verzögerte Zuweisung aufgelöst wurde; das heißt, die Datenblöcke wurden zugewiesen und geschrieben und der Inode aktualisiert (im Speicher), aber nicht unbedingt auf die Festplatte übertragen.

Der Aufruf von [fsync()] erzwingt tatsächlich den Inode auf die Festplatte. Im Fall des ext4-Dateisystems werden mit dem ersten [fsync()] tatsächlich alle Inodes auf die Festplatte übertragen., und alle nachfolgenden [fsync()]-Aufrufe sind tatsächlich No-Ops (vorausgesetzt, die Dateien „a“, „b“ und „c“ befinden sich alle im selben Dateisystem). Das bedeutet aber, dass die Anzahl der (schweren) JBD2-Commits auf ein Minimum reduziert wird.

Es verwendet einen Linux-spezifischen Systemaufruf --- sync_file_range() ---, aber das Ergebnis sollte eine durchgängig schnellere Leistung für alle Dateisysteme sein. Ich betrachte dies daher nicht als einen ext4-spezifischen Hack, obwohl er die Dinge für ext4 wahrscheinlich schneller macht als für jedes andere Dateisystem.

--Ted T'so

Möglicherweise wäre es für ein anderes Dateisystem von Vorteil, stattdessen AIO-fsync()-Operationen zu verwenden.

bcachefs(in Entwicklung) soll IO zwischen verschiedenen Dateien viel besser isolieren als ext4. Das könnte also besonders interessant für einen Test sein.

Es klingt, als ob ext4 für ein reines AIO-fsync()-Muster nicht so gut optimiert ist (ich vermute, andere Dateisysteme könnten die gleiche Einschränkung haben). Wenn das so ist, wäre es vermutlich möglich, zuerst alle gleichen sync_file_range()-Aufrufe auszuführen, dann alle AIO-fsync()-Operationen als zweite Runde zu starten und zum Schluss alle Dateien an die richtige Stelle umzubenennen, wenn die fsync()-Operationen abgeschlossen sind.


ALT:

Der erste Schritt einer solchen Untersuchung sollte eine Messung sein :-).

Es ist möglich, den fsync()-Teil mit zu deaktivieren echo "force-unsafe-io" > /etc/dpkg/dpkg.cfg.d/force-unsafe-io.

Bisher habe ich versucht, es apt-get installunter strace -f -wcin einem Debian 9-Container auszuführen. Wenn ich das aptitudePaket beispielsweise mit „unsafe io“ installiere, gibt es nur 495 synchrone fsync()-Aufrufe. Während bei einer aptitudenormalen Installation 1011 fsync()-Aufrufe vorhanden sind. „unsafe io“ hat den SYNC_FILE_RANGE_WAIT_BEFOREAufruf auch deaktiviert, wodurch die Anzahl der sync_file_range()-Aufrufe von 1036 auf 518 reduziert wurde.

Es war jedoch viel weniger klar, ob dies die durchschnittliche benötigte Zeit reduzierte. Wenn ja, scheint es nicht mehr als die zufällige Abweichung zwischen den Durchläufen zu sein. Bisher habe ich dies auf ext4 und XFS auf einer mechanischen Festplatte getestet.


apt-getbesagt, dass die Gesamtgröße der 518 entpackten Dateien 21,7 MB betrug (siehe Ausgabe unten).

Bezüglich der 495 fsync()-Aufrufe, die auch bei der Anforderung von „unsafe io“ vorhanden blieben:

Auf ext4 zeigte die Strace-Ausgabe eine Zeit von etwa 11 Sekunden für die verbleibenden fsync()-Aufrufe an. Auf XFS betrug die entsprechende Zahl etwa 7 Sekunden. In allen Fällen war dies der Großteil der für die Installation benötigten Zeit aptitude.

Selbst wenn „unsafe io“ also eine kleine Verbesserung bei der Installation bringt aptitude, scheint es, als müsste man es /varauf einem deutlich schnelleren (mit geringerer Latenz) Gerät als dem Rest des Systems installieren, bevor der Unterschied wirklich spürbar wäre. Aber ich bin nicht daran interessiert, diesen Nischenfall zu optimieren.

Die Ausführung unter strace -f -y -e trace=fsync,renamezeigte, dass 2 der verbleibenden fsync()-Aufrufe auf erfolgten /etc/ld.so.cache~und 493 davon sich auf Dateien innerhalb /var/lib/dpkg/der Paketdatenbank bezogen.

318 der fsync()-Aufrufe erfolgen unter /var/lib/dpkg/updates/. Dies sind Inkremente zur dpkg-Datenbank /var/lib/dpkg/status. Die Inkremente werden am Ende des dpkg-Laufs in die Hauptdatenbank hochgerollt („mit einem Checkpoint versehen“).


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.

Antwort1

Die Frage deutet darauf hin, dass dies bei ext4 oder XFS nicht hilft.

Ich habe es auch mit der Installation eines viel größeren Pakets ( linux-image-4.9.0-9-amd64) getestet. Es schien immer noch gleich lange zu dauern, unabhängig von --force-unsafe-io.

ext2

Unter ext2 --force-unsafe-iowurde die Installationszeit linux-imagevon 50 Sekunden auf 13 Sekunden reduziert.

Der Kernel, auf dem ich die Tests ausgeführt habe, war 5.0.17-200.fc29.x86_64, der verwendet CONFIG_EXT4_USE_FOR_EXT2.

Ich habe ext2 mit der Userspace-Implementierung aio_fsync() getestet. Die beste Verbesserung war jedoch nicht von der Verwendung von AIO fsync() abhängig.

Meine Verbesserung war eigentlich auf einen Nebeneffekt zurückzuführen. Ich hatte dpkg so geändert, dass es zuerst alle fsync()-Operationen und dann alle rename()-Operationen ausführte. Während das ungepatchte dpkg nach jeder fsync()-Operation rename() aufrief. Ich verwendete AIO-Warteschlangentiefen von bis zu 256. AIO fsync() mit einer Warteschlangentiefe von 1 war deutlich langsamer als synchrones fsync() – es scheint, als ob es einen gewissen Overhead gab. Die beste Verbesserung erforderte auch, SYNC_FILE_RANGE_WRITEzuerst alle ursprünglichen Operationen auszuführen. Die verbesserte Version ließ sich linux-imagein etwa 18 Sekunden installieren.

Diese Reihenfolge der Operationen ist eigentlich das, was Ted T'so ursprünglich vorgeschlagen hat :-D. Was passiert, ist CONFIG_EXT4_USE_FOR_EXT2, dass fsync() auch hilfreicherweise das übergeordnete Verzeichnis synchronisiert. Sie möchten zuerst alle Dateinamenmanipulationen durchführen, damit Sie mehrere Aktualisierungen auf der Festplatte für jedes Verzeichnis vermeiden können. CONFIG_EXT2Ich glaube nicht, dass dies bei der alten Implementierung oder bei einem normalen ext4Dateisystem passiert .

ext4: Lassen Sie fsync dieses Mal das übergeordnete Verzeichnis wirklich im No-Journal synchronisieren

[...] Dies schließt natürlich auch den Ext2-Standardmodus ein. [...]

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.

Wie zuvor scheint das Ersetzen der fsync()-Phase durch sync() eine beunruhigend gute Leistung zu erbringen, passend --force-unsafe-io:-). sync() oder syncfs() scheinen sehr gut zu sein, wenn man damit durchkommt.

btrfs

Als ich begann, aio_fsync() auf btrfs zu testen, entdeckte ich, dass fsync()-Operationen aufgrund eines kürzlich erfolgten Datenintegritätsfixes dazu führen können, dass rename() der Datei blockiert wird. Ich entschied, dass ich an btrfs nicht interessiert bin.

Warum dauert rename() länger, wenn zuerst fsync() aufgerufen wird?

verwandte Informationen