Бла бла бла про io_uring

Бла бла бла про io_uring

Arelav

Cегодня вспоминал io_uring [1], по работе, и понял, что ни разу не писал в канал про эту абстракцию, которая мне очень нравится.

Ниже речь пойдет только про Linux.


В Linux до относитель недавнего времени (5.1) существовало по сути два способа взаимодействия с файловой системой:


Способные блокировать поток, сисколы (read, write, etc), простое, но неэффективное API прямиком из 70ых (в golang есть интересный хак, когда вы делаете блокирующий сисколл [2], поток тредпула (m), исполняющий горутину, отдает свою очередь горутин (p) шедулеру, и соотвественно другие потоки без очереди могут ее захватить). В плюсах же чаще для файлового IO просто делают отдельный тредпул. Главная проблема этого подхода он не масштабируется, так как мы добавляем порядок, синхронизацию, там где оно не нужно.


Aсинхронное API (aio_*), оно крайне кривое (почему, отдельный разговор), и работает только с не буферизованными файлами. Так что, если вы хотели его использовать, вам нужно было реализовывать собственный кеш для файлов, как итог им пользуются только некоторые базы данных, насколько мне известно.

Также на мой личный взгляд, если вы не считаете, что ваш процесс единственный нагруженный в юзерспейсе ОС, использовать его сомнительно. 


В ядре 5.1 появилось новое API — io_uring.


Некоторая предыстория моего знакомства с этим API:

Года так 3 назад, я задавался вопросом, а можно ли как-то сделать асинхронный fsync append-only файла. Задача была в том, чтобы по событию синхронизировать файл в который писались данные, при этом отсутствовал внешний наблюдатель (то есть смысла ожидать fsync не было, хотелось как бы попросить OS: пожалуйста, если можешь синкани состояние этого файла как можно скорее).


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

Позже его приняли, и разобравшись в том, что это такое, я однозначно могу сказать, что на сегодняшний день эта задача прекрасно решается с помощью io_uring:

1) Пишем write в буфер фиксированного размера

2.1) Периодически флашим его, то есть асинхронно отправляем write c буффером через io_uring интерфейс (тут также понадобится некоторый кеш блоков, так как мы отдаем владение блоком ядру, но это просто список, стек)

2.2) Тут возможна оптимизация, есть два варианта как пользоваться io_uring сабмитить данные самому через сискол, или завести специальный поток на стороне ядра который будет поллить очередь [3].

3) Когда же попросили сделать fsync, мы просто флашим буфер, и хочется сказать "отправляем fsync через io_uring", но нет, ведь ядро, может исполнять запросы в очереди в произвольном порядке, что и делает io_uring действительно быстрым (забавно кстати, что есть некоторые параллели с тем как vulkan отличается от opengl). То есть, если мы просто будем отправлять fsync, то ядро сначала может исполнить его, а затем уже write-ы которые в очереди.

3) Что же тогда делать? Ну можно было бы дождаться завершения всех вызовов в очереди, то есть самим по-poll-ить completion queue, но есть решения лучше, давайте просто запушим nop операцию которая будет с DRAIN флагом [4], такая операция и будет барьером, который не позволит ядру запустить fsync раньше чем исполнить все write, при этом мы не будем ждать (!)


Собственно все, задача решена, самое приятное с точки зрения файлового IO, что все это работает как с буферезованным, так и с нет IO, например никто не мешает использовать mmap файла для рандомных чтений.


Ну и как вишенка на торте, то что помимо правильного API мы получаем самое производительное решение: есть разные бенчмарки, вот например [5]


Пожалуй единственный печальный момент, что даже 5.1 кернел еще далеко не у всех юзеров, как я понимаю enterprise на данный момент это в основном 3-4 кернелы (что конечно пиздец, но спасибо хоть не 2)


Что же с аналогами не из мира linux?


Ну на Windows, довольно быстро сообразили, что тут сделали офигенную штуку и сделали аналог [6], судя по тому, что я видел, разница около косметическая, что если WIndows это уже давно, просто лишняя абстракция над Linux кернелом? Работает же WSL как-то, ощущения что буквально взяли почти как есть io_uring.

Из особенностей, помимо мелких отличий, оно более свежее, так что мануалов, багов и тд, наверно больше. А еще, помоему, нет некоторых фичей, но задел под флаги есть [7], так что думаю это только вопрос времени.


В FreeBSD, macOS, ... ну вы поняли, насколько я знаю аналога нет :(

Ну пожалуй стоит заметить, что kqueue изначально сделан более прямо чем epoll и умеет в fd в отличии от него (epoll всегда возвращает что готов)

Как следствие проблема именно файлового IO там вероятно чуть менее остра? Ну а сокращение количества вызовов сисколов (только сейчас задумался как же странно это звучит), и возможность делать асинхронными не только read/write видимо менее критична на их взгляд. Не знаю, вообще macOS закрытая, а FreeBSD полутруп?


Но говоря про последнее, меня очень привлекает в io_uring именно возможность удешевления сисколов до почти обычных вызовов [8].

Может кто знает, я не интересовался такими маргинальными знаниями раньше: похоже ли io_uring на то, как обычно делают взаимодействие между компонентами в микроядерных ОС?

Но ладно, это неважно, важно, как мне кажется, то что по мере добавления поддержки новых сисколов в io_uring, возможно из Linux-а вынесут к черту большую часть кернел имплемнатции дров? И будет в кернеле не 20+ млн строк, а хотя бы какая-нибудь парочка)


Ладно оставим эти влажные фантазии, надеюсь на какую-то активность в комментах, я старался, а io_uring классный


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


Ссылочки, для тех кому TLDR

[1] Efficient IO with io_uring собственно описание

[2] Функция, которая вызывается в golang для сисколов

[3] Пример с обьяснениями поллинга submission queue

Также это в целом отличный сайт с крутым названием, о том как использовать io_uring и liburing, кстати у автора сайта в блоге есть серия статей c примерами.

[4] IOSQE_IO_DRAIN  

[5] Опыт и бенчмарки ScyllaDB

[6] Windows I/O Rings описание в прикольном блоге, там же есть пост в котором автор пишет о тех различиях, на которые он обратил внимание

[7] Windows I/O Rings дока, как видим пока флажков нет

[8] Доклад про микроядерную ОС: managarm, в общем выглядит красиво

Report Page