Pull to refresh

Comments 36

если он такой кривой, как тогда он окзался в апстриме?
Думаю, этот вопрос лучше задать тому же Линусу или Эндрю Мортону. Судя по гит-репозиторию, AIO в ядре достаточно давно и многие патчи после 2005 года (т.е. после перехода на Git) были signed-off самим Линусом или Мортоном.

На мой взгляд, это нормальная ситуация в больших opensource-проектах — да, есть части кода, которые не очень нравятся основателю. Тем не менее, они удовлетворяют нужды пользователей, не ломают другие части проекта и работают так, как задумано.
Собственно вот то самое письмо Линуса всё объясняет: Oracle работал на Linux'е медленнее, чем на Solaris'е, потому пришлось реализовать что-то похожее.

Это было, понятно, в те времена, когда основной системой на серверах был именно Solaris, а не Linux… и теперь это выкинуть невозможно, так как совместимость же!
А вы не думали об использовании SolarFlare?
Честно говоря, мы не используем это в Badoo — все узкие места в бизнес-логике, а не в обработке сетевых соединений, поэтому нам (пока?) вполне хватает libevent.
А пост — перевод.
Да, к концу статьи я уже упустил, что это не ваши приключения, а перевод )
Я, кстати, на днях пытался найти aio в PHP, не нашёл, зато вышел на эту статью :)
а оно там точно нужно? :) я б вынес такие вещи в какой-то отдельный сервис на чем-то вроде C или Go.
Может и не точно :) У нас проблема интересная — в некоторых ситуациях при чтении с NFS, если сервер пропадает, клиент NFS намертво зависает где-то в ядре, вместо с процессами, дерзнувшими в этот момент обратиться к этой файловой системе.

Так накрепко зависает, что минус-девять не помогает.

Мы придумали несколько решений и костылей, aio было в числе кандидатов.
я б на вашем месте смотрел в сторону решения, которое предусматривает отсутствие NFS. мы выпилили NFS вообще везде примерно лет так 8 назад и ничуть не жалеем. сейчас сетевых FS и хранилищ разных много, слава богу.
Да, это одно из направлений по которому мы работаем. Посмотрим что получится :)
UFO just landed and posted this here
Такой путь мы тоже рассматриваем. Правда с реализацией от Фейсбука.
> Так накрепко зависает, что минус-девять не помогает.

Точно не забыли опции soft, intr при монтировании?
soft не рекомендуется же. Про intr спрошу у ребят.
Soft mount не рекомендуется для использования в общем случае, поскольку прикладная программа может быть не готова обрабатывать внезапные ошибки доступа к файлам.

Если предполагается, что с файлами на запись будет работать только ваша собственная программа, и возможность ошибки на каждой стадии будет предусмотрена — soft вполне себе допустимый вариант.

В случае же read-only сетевая nfs-шара в режиме soft mount и вовсе ничем принципиально не отличается от, к примеру, обычного cd-rom.
По второй опции вот что они сказали:
The intr / nointr mount option is deprecated after kernel 2.6.25.
В Линуксе вроде как уже давно существует библиотека libaio (пакет Debian libaio1), которую как раз очень хочет Oracle при установке. Как я понял при беглом просмотре это как раз обертка для системных вызовов.

Похоже, только автор исходной статьи вроде ей не пользовался.
П.С. В дебиане указан другой источник https://pagure.io/libaio

Вот здесь я искренне завидую разработчикам под Windows.
Создал IOCP, привязал сокет к нему, засабмитил несколько WSARecv(), причём можно подряд на один сокет — они будут отрабатываться в порядке поступления.
При отправке — никакого ожидания. Дальше слушаешь поступление сообщений в IOCP, по адресу OVERLAPPED опознаёшь выполненный запрос, разбираешь ответ.
Абсолютно честный проактор без странностей типа «ну мы вроде отправляем асинхронно, но на самом деле — вы тут и заблокируетесь». Ядро умеет опознавать такие заранее подсунутые буфера и складывать в них данные без копирования через промежуточный буфер — ещё с тех времён, когда большинство юниксов про zero-copy только мечтали.
Есть, конечно, проблемы и тут. Перемещать хэндлы между IOCP для баланса нагрузки — есть только недокументированный путь. Сокеты не во всех ожиданиях сочетаются с другими файлоподобными объектами. Нельзя заранее узнать или регулировать количество доступных активных асинхронных запросов. Но если с ними справиться — система работает на максимуме эффективности без лишних потерь.

Что Линусу не нравится в проакторной асинхронности — понять невозможно, а описать внятно без всех этих crap/horrible/etc. он не в состоянии. Когда-то он точно так же ругался на kqueue про «silly triplet», в результате принял в ядро то же самое, но кривее и несовместимо, под названием epoll. Здесь он не хочет ни один вариант AIO впускать полноценно (чтобы оно не блокировалось); боится плодить ядерные нити и FSM на сокеты? Честно непонятно.

Люди, а за что минусуете? Человек дело говорит. Меня тоже всегда удивляло, что такое сильное ядро не умеет полноценно асинхронно работать, тогда как есть пример как это нормально реализовано в другом ядре.

Не минусовал, но предположу.
IOCP легко эмулируется через epoll(), есть статья сравнения на хабре habr.com/ru/company/infopulse/blog/415403
т.е. достаточно написать свой небольшой враппер — и удобство точно такое же.

По скорости медленнее тоже врядли будет, в свое время я перепробовал целую кучу методов, включая IOCP, TransmitFile, WSAEnumNetworkEvents… В простом кейсе легкий запрос-ответ, на винде было примерно 60к rps против 110k rps на лине на той же машине.
Я даже пробовал недокументированные извращения с Nt* функциями, но результата это не дало.

А вот основная проблема, что open() и CreateFile() обращаются к метаданным на винте и тоже могут повиснуть — так и остается проблемой, приходится использовать пул потоков.

Спасибо за разъяснения. А почему тогда такие простые, судя по вашим словам, обёртки недоступны по умолчанию в линуксе? Вот вывод логов через rsyslog из контейнеров блокируется? Потому что мы в ЦИАН уже получали проблемы от этого пару лет назад. Возможно, что-то изменилось или мы что-то не так готовили, но при резком увеличении логов у нас питон глох, если logstash не успевал принимать логи с rsyslog машины.
Ну и про rps — асинхронность она же не про rps, а про масштабирование io heavy приложений (современные микросервисы) и их использование cpu на это дело.

При заполнении отведённого под логи буфера остаётся только два варианта: подождать или выкинуть то что не влезло. Это не зависит от асинхронности и количества потоков.

Это то да, но там, насколько я помню, было то, что даже отправка в syslog из приложения (чтобы на том конце уже дропнули в режиме overflow) было блокирующей операцией и это деградировало отработку запросов в приложении. И это мы говорим про udp! Блокирующая отправка по udp, Карл! Вот это меня удивляет.

Про обертки не понял, обертки есть, одна из известных — Boost.Asio. Если говорить про низкий уровень — то скорее у всех узкое место open() и CreateFile().

rsyslog честно не знаю как он оптимизирован, но я вижу проект не такой простой, там и шифрование есть github.com/rsyslog/rsyslog/tree/master/runtime
Я бы первым делом посмотрел strace (или htop + s на треде) и perf питоновского процесса на отсылке и сервера что шлет в сеть. Узкое место наверняка найдется.
Так же вижу вот такие баг репорты bugzilla.redhat.com/show_bug.cgi?id=1047039
там в комментах видно использование select(), что уже недопустимо. Вполне возможно все решается конфигурацией.
У IOCP есть преимущество следующего вида — IOCP «из коробки» поддерживает многопоточность, когда все потоки, читающие из completion port считаются Windows, как единый пул потоков с LIFO планированием. Т.е. создал completion port, навесил на него все сокеты принимаемых входящих соединений (случай сервера), создал пул потоков, где каждый поток читает по одному событию из completion port и получил почти равномерную (LIFO все таки) загрузку всех потоков из пула (при полной загрузке сервера, при частичной загрузке — за счет LIFO часть потоков может и простаивать, если задач — completion packets — им попросту «не достается»).

При попытке выстроить подобную архитектуру с единственным epoll instance придется использовать mutex. Это убивает всякое планирование потоков (и оно вовсе не LIFO, которое Windows IOCP как бы обещает где-то в документации) и серьезно просаживает производительность. Поэтому с epoll поступают иначе — создают несколько epoll instances (например, по кол-ву ядер CPU) и с каждым epoll instance работает только один поток без каких-либо mutex.

Вот только наличие нескольких epoll instance уже требует распределения (а распределение может требовать и учета — зависит от алгоритма) сокетов по этим нескольким epoll instance. Неудачное распределение может привести к перекосам в нагрузке потоков (Why does one NGINX worker take all the load?) — когда одни потоки перегружены, а другие простаивают (в то время как в других epoll instance есть необработанные события).

Насчет эмуляции реактора в IOCP (для тех, кто не хочет выделать буфер заранее) — тот же Boost.Asio реализвал это поверх IOCP через null_buffers (Reactor-Style Operations).

Проблема со скоростю многопоточности у epoll() действительно была. Но в кокой-то момент она была решена, по-моему даже после EPOLLEXCLUSIVE были косяки, но сейчас я проблем не наблюдаю — треды загружаются равномерно.
Судя по epoll_ctl manual page EPOLLEXCLUSIVE ввели, начиная с версии ядра 4.5. Не все могут позволить себе такое. Например, весь RHEL 7 — это Kernel Version 3.10.x (хотя можно и обмануть).

Похоже, нужно почитать про epoll — даже что-то вводное типа Вся правда о linux epoll уже говорит о том, что, возможно, с многопоточностью (pre-fork) все работает, если готовить определенным образом. Я не уверен, что такой рецепт подходит для всех — вроде как есть причины, по которым та же Asio все еще не использует edge triggered режим.
Ну это верно. Вообще RHEL/Centos по ощущениям может быть консервативнее дебиана, я помню ужаснулся когда увидел версию gcc там, зато его админы частенько любят. Чтобы что-то новое там использовать его готовить надо, обязательно epel подключать итд. А вот в OpenSUSE/Ubuntu/Arch все поновее и таких проблем нет.

Вообще на версию ядра в RHEL смотреть не стоит. Нужно смотреть на подсистемы. Я поддерживаю драйвер для наших железок, так иногда они просто целые подсистемы бекпортируют (сталкивался, когда меняли подсистемы и мы завязались на проверку по версии ядра: если старое, использовать старый подход, если новое — новый и обломались, когда стали собирать на обновлённой CentOS...). Так что вполне может статься, что EPOLLEXCLUSIVE там благополучно присутствует после некого увеличения внутреннего номера ядра.

> по-моему даже после EPOLLEXCLUSIVE были косяки, но сейчас я проблем не наблюдаю — треды загружаются равномерно.

Равномерно на полную или равномерно по чуть-чуть? С точки зрения затрат процессора на переключение, вымывание кэшей и т.п. — второе сильно невыгодно. Тут LIFO в IOCP сделано изначально умно.
> IOCP легко эмулируется через epoll(), есть статья сравнения на хабре

Сравнение — есть. Но рассказа про «лёгкость эмуляции» — нет, вы что-то совсем не то прочитали.

Лёгкости эмуляции не может быть уже потому, что epoll сам по себе в принципе не может поддерживать проактивный заказ операции, он работает только в реактивном режиме. Для сокетов, пайпов и т.д. — такой режим годится: включили «мы ждём каких угодно данных» и получаем. Но уже заказать чтение файла с конкретной позиции — невозможно: там операция на момент заказа должна быть специфицирована с указанием позиции и размера. Select, poll на файлах или устройствах прямого доступа не работает (select всегда возвращает готовность, хотя это кривое легаси: по-нормальному он должен был бы отшибать с EINVAL). Потому и были придуманы aio_read с компанией.

Это было про чисто факт возможности операции. Второе — эффективность. Если вы проактивно заказали чтение порции байт из сети в пользовательский буфер, ядро может выбрать, как ему эффективнее и насколько оно вообще умеет и умеет железо:
1) прочитать из буфера сетевухи в сокетный буфер в ядре, потом копировать в пользовательский буфер — два копирования;
2) скопировать из буфера сетевухи в пользовательский буфер напрямую — одно копирование;
3) скомандовать сетевухе «читать TCP в этот буфер» и задать пользовательский буфер — ноль копирований.

С epoll и аналогами у вас только (1). С IOCP можно даже потребовать выставить сокетный буфер в ноль байт и всё общение производить с пользовательскими буферами.
То же самое для отправки в сеть. Меньше тупых копирований — меньше загрузка процессора, меньше ограничений в скорости.

> В простом кейсе легкий запрос-ответ, на винде было примерно 60к rps против 110k rps на лине на той же машине.

Лёгкий запрос-ответ — да, верю — тут цена сисколла уже может влиять, на Linux она очень жёстко минимизируется, на Windows — есть, пишут, неустранимые препятствия. А на плотном одностороннем или двустороннем потоке (предположим потоковое прокси, которое релеит через себя)?

> А вот основная проблема, что open() и CreateFile() обращаются к метаданным на винте и тоже могут повиснуть — так и остается проблемой, приходится использовать пул потоков.

Эта проблема везде. Squid использовал для задачи удаления пул unlinkd-процессов :)
Ну тут вы правы, epoll() только ожидание и только вариант (1) и придется делать доп. сискол для чтения. Однако я делал и то и то и на винде у меня впринципе не получалось добиться приемлимой производительности. А io_uring как раз решит и эту проблему и доп. сисколов не понадобится, но он пока не широко доступен в продакшене.
Sign up to leave a comment.