Задержка асинхронного ввода-вывода io_submit в Ubuntu Linux

Я ищу совет о том, как получить эффективный и высокопроизводительный асинхронный ввод-вывод, работающий для моего приложения, работающего на Ubuntu Linux 14.04.

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

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

Мы экспериментируем с реализацией, которая использует io_submit для выдачи запросов на запись и чтение. Буду признателен за любые предложения или отзывы о наилучшем подходе к нашему требованию. Io_submit собирается дать нам лучшую производительность, чтобы встретить нашу цель? Что следует ожидать для задержки каждой записи io_submit и задержки каждой операции чтения io_submit?

Используя наш экспериментальный код (работающий на 2,3 ГГц Haswell Macbook Pro, Ubuntu Linux 14.04), мы измеряем около 50 пусков для записи io_submit при расширении выходного файла. Это слишком долго, и мы даже не приблизились к нашим требованиям к производительности. Будем весьма благодарны за любые рекомендации, которые помогут мне запустить запрос на запись с наименьшей задержкой.

2 ответа

Linux AIO - это что-то вроде черного искусства, когда опытные практики знают, что такое ошибки, но по какой-то причине запрещено говорить с кем-то о том, что они еще не знают. Исходя из работы в Интернете и опыта, я привел несколько примеров, когда асинхронная отправка ввода-вывода в Linux может становиться (тихо) синхронной (что приводит к io_submit() в блокирующий вызов):

  1. Вы отправляете буферизованный (или не прямой) ввод / вывод. Вы находитесь в зависимости от кэширования Linux, и ваша отправка может идти синхронно, когда то, что вы запрашиваете, еще не находится в кэше чтения / записи, и новый запрос не может быть принят до завершения обратной записи.
  2. Вы просили прямой ввод-вывод файла в файловой системе, но по какой-либо причине файловая система решает игнорировать O_DIRECT "подсказка" (например, как вы представили ввод / вывод не соответствует O_DIRECT ограничения выравнивания, файловая система или конфигурация конкретной файловой системы не поддерживает O_DIRECT) и молча возвращается к буферизованному вводу / выводу, что приводит к описанному выше случаю.
  3. Вы выполняете прямой ввод / вывод в файл в файловой системе, но файловая система должна выполнять синхронную операцию (например, обновление метаданных), чтобы выполнить ввод / вывод. Некоторые файловые системы, такие как XFS, стараются обеспечить хорошее поведение AIO по сравнению с другими файловыми системами, но даже в этом случае пользователь должен быть очень осторожным, чтобы избежать операций, которые будут вызывать синхронизацию.
  4. Вы отправляете слишком много выдающихся операций ввода-вывода. Ваш контроллер диска / диска будет иметь максимальное количество запросов ввода-вывода, которые могут быть обработаны одновременно. Максимальные размеры очереди запросов AIO для каждого конкретного устройства (см. /sys/block/[disk]/queue/nr_requests документация) И системное максимальное максимальное количество запросов AIO (см. /proc/sys/fs/aio-max-nr документация) внутри ядра. Создание резервных копий ввода-вывода и превышение размера очередей ядра приводит к блокировке.
    • Подпунктом является: если вы отправляете ввод / вывод, которые являются "слишком большими" (то есть больше, чем /sys/block/[disk]/queue/max_sectors_kb) они будут разделены в ядре и продолжат обрабатывать более одного запроса...
  5. Слой в стеке блочных устройств Linux между вами и отправкой на диск должен блокироваться. Например, такие вещи, как программный RAID-массив Linux (md), могут заставить запросы ввода-вывода, проходящие через него, останавливаться, пока он обновляет свои метаданные RAID 1 на отдельных дисках.
  6. Ваше представление заставляет ядро ​​ждать, потому что:
    • Требуется взять определенный замок, который используется.
    • Необходимо выделить дополнительную память или страницу.

Приведенный выше список не является исчерпывающим.

Проблеск надежды на будущее - это предлагаемая функция, которая позволяет программе запрашивать лучшее поведение, устанавливая флаг, который приводит к сбою отправки AIO с EAGAIN, если он продолжит блокировать. На момент написания статьи были написаны патчи AIO EAGAIN для ядра 4.13. Даже если эти патчи войдут в будущее ядро, вам все равно а) понадобится упомянутое (или более позднее) ядро, чтобы использовать эту функцию, и б) должно быть известно о случаях, которые оно не охватывает (хотя я замечаю, что существуют совершенно отдельные патчи). предложил попытаться вернуть EAGAIN, когда блокированные устройства в стеке вызовут блокировку).

Рекомендации:

Связанные с:

Надеюсь, этот пост кому-нибудь поможет.

Я говорю как автор предлагаемого Boost.AFIO здесь.

Во-первых, Linux KAIO (io_submit) почти всегда блокируется, если не включен O_DIRECT и не требуется выделение экстентов, а если включен O_DIRECT, необходимо читать и записывать кратные 4 КБ на границах, выровненных по 4 КБ, иначе вы заставляете устройство выполнять чтение -Изменение-запись. Поэтому вы не получите ничего, используя Linux KAIO, если не реорганизуете свое приложение так, чтобы оно было совместимо с вводом-выводом в формате O_DIRECT и 4 КБ.

Во-вторых, никогда не расширяйте выходной файл во время записи, вы форсируете распределение экстентов и, возможно, сброс метаданных. Вместо этого сократите максимальный экстент файла до некоторого достаточно большого значения и сохраните внутренний атомарный счетчик конца файла. Это должно уменьшить проблему до просто распределения экстентов, которое для ext4 является пакетным и ленивым - что более важно, вы не будете принудительно сбрасывать метаданные. Это должно означать, что KAIO на ext4 будет работать асинхронно большую часть времени, но непредсказуемо будет синхронизироваться, поскольку сбрасывает отложенные выделения в журнал.

В-третьих, я бы, вероятно, подошел к вашей проблеме, используя атомарное добавление (O_APPEND) без O_DIRECT или O_SYNC, поэтому вы добавляете обновления в постоянно растущий файл в кэш страницы ядра, что очень быстро и безопасно для параллелизма. Затем вы время от времени собираете мусор, какие данные в файле журнала устарели, и чьи экстенты могут быть освобождены с помощью функции fallocate(FALLOC_FL_PUNCH_HOLE), чтобы физическое хранилище не росло вечно. Это выдвигает проблему объединения записей в хранилище в ядро, где много усилий было потрачено на создание такой быстрой записи, и поскольку запись выполняется всегда вперед, вы увидите, что записи попадают в физическое хранилище в довольно близком порядке к той последовательности, в которой они были записаны, что делает восстановление потери мощности простым. Этот последний вариант заключается в том, как это делают базы данных, и, в действительности, это делают журналирующие системы хранения, и, несмотря на возможную существенную модернизацию вашего программного обеспечения, вам потребуется этот алгоритм, который доказал наилучшее соотношение задержки и долговечности в случае проблем общего назначения.

В случае, если все вышеперечисленное кажется большой работой, ОС уже предоставляет все три метода, объединенные в хорошо настроенную реализацию, которая более известна как карты памяти: 4 / К, выровненный ввод-вывод, O_DIRECT, никогда не расширяющий файл, все асинхронный ввод / вывод. В 64-битной системе просто выделите файл на очень большой объем и отобразите его в памяти. Читайте и пишите так, как считаете нужным. Если ваши шаблоны ввода / вывода запутывают алгоритмы страниц ядра, что может произойти, вам может понадобиться прикосновение к madvise() здесь и там, чтобы поощрить лучшее поведение. Меньше значит больше с madvise(), поверь мне.

Очень много людей пытаются дублировать mmaps, используя различные алгоритмы O_DIRECT, не понимая, что mmaps уже может делать все, что вам нужно. Я бы посоветовал изучить их в первую очередь, если Linux не будет работать, попробуйте FreeBSD, которая имеет гораздо более предсказуемую модель файлового ввода-вывода, и только потом углубляйтесь в сферу применения своего собственного решения ввода-вывода. Говоря как кто-то, кто делает это в течение всего дня, я настоятельно рекомендую вам избегать их, когда это возможно, системы хранения документов - это ямы с дьяволами странного и странного поведения. Оставьте бесконечную отладку кому-то еще.

Другие вопросы по тегам