Может ли AIO fsync улучшить производительность dpkg?

Может ли AIO fsync улучшить производительность dpkg?

Может ли dpkgменеджер пакетов Debian добиться заметного улучшения производительности, используя одну из операций AIO fsync() вместо sync_file_range() + fsync()?

[Предлагаемый] API fsync2() по сути идентичен существующему API AIO_FSYNC/AIO_FDSYNC, за исключением того, что он синхронный, а именно этого приложения хотят избежать.

Единственный аргумент, который мне привели против [использования] AIO_FSYNC, заключается в том, что "реализация — это просто очередь задач", что в значительной степени бессмысленно, поскольку она не зависит от реализации файловой системы, но позволяет автоматически распараллеливать все операции fsync на стороне ядра. Это позволяет файловой системе(ам) автоматически оптимизировать ненужные записи в журнал при завершении параллельных операций fsync — XFS, ext4 и т. д. уже делают это, когда пользовательские приложения запускают fsync() одновременно из множества процессов/потоков.....

Эта простая реализация позволяет реализовать простую рабочую нагрузку «распаковка с aio fsync» (т. е. «запись множества файлов по 4 КБ и aio_fsync() пакетами по мере выполнения, удаление завершенных fsync() перед отправкой нового пакета») на XFS, увеличив производительность примерно с 2000 файлов/с (ограниченная задержка ввода-вывода синхронной записи) до более чем 40 000 файлов/с (ограниченная скорость записи IOPS на внутреннем хранилище).

--Дэйв Чиннер

Пример рабочей нагрузки имеет сходство с apt-get installили dpkg -i(отчасти в зависимости от размера файлов в установленных пакетах :-). dpkgдолжен эффективно fsync() всех распакованных файлов, прежде чем переименовывать их на место.

dpkgоптимизирован с использованием советов Теда Т'со. Оптимизация заключается в добавлении вызовов sync_file_range() в определенных точках. Этот системный вызов делаетнетпредоставляет те же гарантии, что и fsync(). Пожалуйста, прочтите документацию дляsync_file_range()и обратите внимание на заметное предупреждение :-).

Ни одна из этих операций не записывает метаданные файла. Поэтому, если приложение не выполняет строго перезапись уже созданных дисковых блоков, нет никаких гарантий, что данные будут доступны после сбоя.

dpkgзапускает обратную запись данных сразу после записи каждого файла, используя SYNC_FILE_RANGE_WRITE. Сначала он записывает все файлы для пакета. Затем идет второй проход по файлам, который ждет обратной записи данных, используя SYNC_FILE_RANGE_WAIT_BEFORE, вызывает fsync(), и, наконец, переименовывает файл на место.

Смотреть коммиты:

Моя гипотеза заключается в том, что распараллеливание операций fsync() может повысить производительность, позволяя более эффективно группироватьметаданныезаписи, в частности пакетная обработка связанных барьеров/очисток кэша диска, которые необходимы для обеспечения постоянной согласованности метаданных на диске.

EDIT: Кажется, моя гипотеза была слишком простой, по крайней мере при использовании файловой системы ext4:

Вторая серия вызовов sync_file_range() с операцией SYNC_FILE_RANGE_WAIT_BEFOREбудет блокироваться до тех пор, пока не завершится ранее инициированная обратная запись. Это в основном гарантирует, что отложенное выделение было разрешено; то есть блоки данных были выделены и записаны, а inode обновлен (в памяти), но не обязательно вытеснен на диск.

Вызов [fsync()] фактически принудительно скопирует inode на диск. В случае файловой системы ext4 первая функция [fsync()] фактически перенесет все иноды на диск., и все последующие вызовы [fsync()] на самом деле являются пустыми операциями (предполагая, что файлы 'a', 'b' и 'c' находятся в одной файловой системе). Но это означает, что это минимизирует количество (тяжеловесных) коммитов jbd2 до минимума.

Он использует системный вызов, специфичный для Linux --- sync_file_range() --- но результатом должна быть более высокая производительность по всем направлениям для всех файловых систем. Поэтому я не считаю это хаком, специфичным для ext4, хотя он, вероятно, делает вещи быстрее для ext4, чем для любой другой файловой системы.

--Тед Т'со

Возможно, какая-то другая файловая система выиграет от использования операций AIO fsync().

bcachefs(в разработке) утверждает, что изолирует IO между различными файлами гораздо лучше, чем ext4. Так что это может быть особенно интересно протестировать.

Похоже, что ext4 не так хорошо оптимизирована для чистого шаблона AIO fsync() (я полагаю, что другие файловые системы могут иметь те же ограничения). Если так, то я полагаю, что можно было бы сначала выполнить все те же вызовы sync_file_range(), затем запустить все операции AIO fsync() во втором раунде и закончить переименованием всех файлов на месте по завершении операций fsync().


СТАРЫЙ:

Первым шагом в таком исследовании должно стать измерение :-).

Часть fsync() можно отключить, используя echo "force-unsafe-io" > /etc/dpkg/dpkg.cfg.d/force-unsafe-io.

До сих пор я пытался запустить apt-get installпод strace -f -wc, в контейнере Debian 9. Например, при установке aptitudeпакета с использованием "unsafe io" происходит всего 495 синхронных вызовов fsync(). В то время как при aptitudeобычной установке происходит 1011 вызовов fsync(). "unsafe io" также отключил вызов SYNC_FILE_RANGE_WAIT_BEFORE, уменьшив количество вызовов sync_file_range() с 1036 до 518.

Однако было гораздо менее ясно, уменьшило ли это среднее время выполнения. Если и уменьшило, то, похоже, не более чем на случайную разницу между запусками. До сих пор я тестировал это на ext4 и XFS на механическом HDD.


apt-getсообщает, что общий размер 518 распакованных файлов составил 21,7 МБ (см. вывод ниже).

Что касается 495 вызовов fsync(), которые остались даже при запросе «небезопасного ввода-вывода»:

На ext4 вывод strace показал время, потраченное на оставшиеся вызовы fsync(), около 11 секунд. На XFS соответствующий показатель составил около 7 секунд. Во всех случаях это было большую часть времени, затраченного на установку aptitude.

Так что даже если "unsafe io" дает небольшое улучшение для установки aptitude, похоже, вам нужно будет /varсмонтировать его на значительно более быстром (с меньшей задержкой) устройстве, чем остальная часть системы, прежде чем разница станет действительно заметной. Но я не заинтересован в оптимизации этого нишевого случая.

Запуск strace -f -y -e trace=fsync,renameпоказал, что из оставшихся вызовов fsync() 2 были на /etc/ld.so.cache~, а 493 из них были к файлам внутри, /var/lib/dpkg/т.е. к базе данных пакетов.

318 вызовов fsync() находятся под /var/lib/dpkg/updates/. Это приращения к базе данных dpkg /var/lib/dpkg/status. Приращения сворачиваются в основную базу данных («контролируются») в конце запуска 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.

решение1

Вопрос предполагает, что это не поможет на ext4 или XFS.

Я также протестировал установку одного гораздо большего пакета ( linux-image-4.9.0-9-amd64). Казалось, что это все равно заняло то же время, независимо от --force-unsafe-io.

ехт2

На ext2 --force-unsafe-ioвремя установки сокращено linux-imageс 50 до 13 секунд.

Ядро, на котором я проводил тесты 5.0.17-200.fc29.x86_64, использовало CONFIG_EXT4_USE_FOR_EXT2.

Я протестировал ext2 с использованием реализации aio_fsync() в пользовательском пространстве. Однако наилучшее улучшение не зависело от использования AIO fsync().

Мое улучшение на самом деле было вызвано побочным эффектом. Я изменил dpkg так, чтобы он сначала выполнял все операции fsync(), а затем все операции rename(). В то время как непатченный dpkg вызывал rename() после каждой fsync(). Я использовал глубину очереди AIO до 256. AIO fsync() с глубиной очереди 1 был значительно медленнее синхронного fsync() — похоже, были некоторые накладные расходы. Лучшее улучшение также требовало SYNC_FILE_RANGE_WRITEсначала выполнения всех исходных операций. Улучшенная версия установилась linux-imageпримерно за 18 секунд.

Этот порядок операций на самом деле изначально предлагал Тед Т'со :-D. Происходит то, что на CONFIG_EXT4_USE_FOR_EXT2, fsync() также услужливо синхронизирует родительский каталог. Сначала вы хотите выполнить все манипуляции с именами файлов, чтобы избежать множественных обновлений на диске для каждого каталога. Я думаю, что этого не происходит в старой CONFIG_EXT2реализации или в обычной ext4файловой системе.

ext4: на этот раз сделайте fsync для реальной синхронизации родительского каталога в no-journal

[...] Это также, очевидно, включает режим ext2 по умолчанию. [...]

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.

Как и прежде, замена этапа fsync() на sync(), похоже, обеспечивает пугающе хорошую производительность, соответствующая --force-unsafe-io:-). sync() или syncfs() кажутся очень хорошими, если вы можете обойтись без их использования.

btrfs

Когда я начал тестировать aio_fsync() на btrfs, я обнаружил, что операции fsync() могут привести к блокировке rename() файла из-за недавнего исправления целостности данных. Я решил, что btrfs мне неинтересен.

Почему rename() выполняется дольше, если fsync() вызывается первым?

Связанный контент