Pull to refresh

Comments 58

Погодите, а что, nginx не использует aio_* при общении с диском?!
Я об этом писал в статье. На линукс все плохо. См. lse.sourceforge.net/io/aio.html — с тех пор не продвинулись.
aio_*() просто обертка над всем этим.
… вот вроде ж читал статью, а этот абзац как-то проглядел.

Спасибо! Шок, это по-нашему. Я использовал асинхронный ввод-вывод в ТойЧтоНельзяУпоминать — там, конечно, кода вагон, но работало весьма стабильно…
Наверное я тут перепутал с libaio. То POSIX AIO, что сейчас в glibc — это тоже обертка, но уже над тредами и довольно примитивная, чтобы её использовать для серьезных задач.
А вы не в курсе, под OSX тут как дело обстоит? Я сходу попробовал погуглить, но, видимо, т.к. я не совсем в теме, ничего полезного не нашлось :(
OSX не серверная система. Не стоит пытаться использовать её в этом качестве, можно наступить на огромное количество различных граблей.
Есть хостеры, которые продают серверы с OSX server.
Что не отменяет написанного мной выше. =)

Мы поддерживаем OSX как платформу для разработки, поскольку многие программисты пользуются макбуками. Но граблей там порядочно. Вот из последнего, не так давно натыкались на ту же проблему, что и разработчик Rust: erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug — банально баг в ядре, связанный с race-condition, который приводит к тому, что попытка записи в сокет может вызывать ошибку EPROTOTYPE, которую никто в этом месте не ожидает увидеть.

И подобного там достаточно, и подозреваю достаточно того, о чем еще неизвестно, но сразу повылезает при попытке использовать OSX под более-менее серьезной нагрузкой.
Ну так то да, но сервера с OSX таки попадаются у клиентов.
Теперь есть еще один повод советовать сменить, спасибо :)
Офигительный перевод Бартенева, сделаный Бартеневым )) Неделя nginx продолжается… Ура!
Я так понял, что теперь будет проще на IOCP переписать под Windows?
Нет. IOCP тут ортогонально. Прелесть данного механизма пулов потоков заключается в том, что он абсолютно независим от основного процесса обработки событий, не использует с ним разделяемых ресурсов, кроме двух очередей, что позволяет практически избежать локов и вмешательств в основные структуры.

Кроме того он реализован только под *nix.
Так через асинхронные функции с OVERLAPPED (под Windows) не будет достигнут такой же эффект?
Почитал про overlapped. Кажется это скорее про реализацию нормального ядерного AIO и отдельные потоки там не нужны. Но я могу ошибаться, поскольку плохо знаком с windows даже на уровне пользователя.

Возможно sebres сможет ответить.

Важно понимать, что текущая реализация пулов не делает nginx многопоточным в плане обработки событий, он по-прежнему обрабатывает события на дескрипторах асинхронно и в одном потоке на рабочий процесс. А отдельные потоки из пулов используются исключительно для выгрузки долгих блокирующих операций, при этом они изолированы от основного потока через две сериализованные очереди (очередь ожидающих обработки заданий и очередь выполненных заданий).
Да, на Windows OVERLAPPED это корректный ядерный AIO.
Так то оно так. И это правда реактор на AIO. Просто тонкости реализации и «тонкие» настройки там играют очень заметную роль. Много и много фрагильнее линуксовых. Например в свое время была такая библиотека как ACE, так там до какой-то версии реактор acceptor-ов «конфликтовал» с реактором worker-ов. Ужасно бажная была реализация.
Проблема еще в том, что под win GetOverlappedResult (или WSAGetOverlappedResult) многопоточно, это не тоже самое что GetOverlappedResult многопроцессно. Вся разница в том, что оно изначально заточено под пул потоков и там более менее оптимально «ядерно» работает. В случае же много процессов есть небольшой конфликт в ядре в генераторе события и обертка над наследуемыми сокетами там в одном месте как-бы настолько синхронна (и насколько помню даже не атомарно), что не просто context switch, а процессы тупо «распараллеливаются» — это не то, чтобы узкое горлышко — по моему такое называют порогами (как на реке), т.е. потоки разных процессов их проходят друг за другом.
Кто погоняет такой реактор SoftICE-ом с дебажным win-kernel, откроет для себя много нового по поводу thread != process под windows.
при этом они изолированы от основного потока через две сериализованные очереди (очередь ожидающих обработки заданий и очередь выполненных заданий).
А что если их на динамических ринг-буферах переписать (возможно чанками), я имею ввиду очереди задач, т.е. ngx_thread_task_post, ngx_thread_pool_cycle и иже с ними.
Один писатель же… Ну а задачи забирать в пуле атомарно проставляя start/end.

Я бы уже повозился с этим, только последнее время все винда — а там нет notify (ну event method не тот — т.е. пул не работает от слова совсем, а жаль). Хотя непонятно почему, ведь и прослушка т.е. листенер организована polling-ом, и цикл как в *ксах крутится, почему бы в том цикле не сделать и вызов событий из ngx_notify, непонятно…
Не думаю что сложно должно быть (потирая руки).
У меня изначально там были безлоковые очереди с семафорами, но благодаря стараниям всем известного персонажа их пришлось выкинуть, дескать слишком сложно. А с condition variables у нас там все равно уже лок есть.

Так что не рекомендую даже пытаться — зарежет.
Это да — зверь человек!
Но у меня чукчи жеж своя форка имеется :).

А можно поподробнее про безлоковые-семафорные, можно PR-ом сюда :) Ну или куском кода в личку бросить (просительно-умалятельный смайл).
у нас там все равно уже лок есть
Тот другой всем локам-лок:
  • во первых он на пуле (1+N конкурентов), а на «condition variables» будет если не ошибаюсь 1+1
  • ну а во вторых один или два лока — две большие разницы (с)

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

Я даже аргументы искать не буду, согласен с тобой — как минимум бесполезно, да и нервы жалко. У меня по работе своих таких сладкая парочка имеется. Больно надо мне еще и с вашим бодаться ;)
Про нервы — это очень верно подмечено. Очень много их на это уходит.
Пулы потоков это хорошо, но проблема еще и в том, что нет возможности узнать чем занимается конкретный поток. Быть может он ждет на блокирующей операции, а процессор в результате простаивает. К сожалению, системного вызова который бы сообщил состояние thread в Linux нет.
Так задача потока в пуле и заключается в том, чтобы ждать на блокирующей операции.
Если использовать их только для этого то все ок (как у вас указано — для обслуживания read()) но если добавить что-то еще то возможны варианты.

Я сталкивался со сценариями типа: часть запросов требует интенсивного счета, другая часть — ввода/вывода (т.е. может блокироваться). Если все потоки из пула уже заблокировались, то вроде как имеет смысл добавить новых, для выполнения счетных операций. Но поскольку знать состояние потока мы не можем, то непонятно когда это делать.

Разумеется, пока вы используете пулы потоков только для ожидания — все более-менее ок.
Для интенсивного счета можно будет настроить отдельный пул потоков. NGINX позволяет создать их сколько угодно. Зачем смешивать все операции в одну кучу? У него будет своя очередь и необходимое количество тредов.
Возможно вы и правы, и я хочу странного.
Просто хочется меньше задумываться над спецификой.
Спасибо за статью. А какие блоги можете посоветовать nginx internals с точки зрения разработки/доработки модулей? Из того, что находил хорошего — Evan Miller, agentzh и что-то у Joshua Zhu.
Пожалуй к этому списку нечего больше добавить. Самый лучший nginx internals постигается чтением исходников.

Есть ещё выступление Валерия Холодкова на HL++ 2008 и статья по мотивам: www.grid.net.ru/nginx/nginx-modules.html и его же блог, посвященный исследованиям внутренностей nginx: www.nginxguts.com
lkml.org/lkml/2015/3/16/891 — Уже 7 версию пытаются… Andrew Morton конечно бухтит но и смысл в его словах есть.
Нам для Postgres это то же нужно, что бы адекватнее косты подсчитывать (fincore в смысле). Надо как то их всех пнуть.
Да, я пару месяцев назад общался с Милошем. С его патчами нам будет не хватать sendfile(). В этом отношении подход Мортона чуть более универсален, хотя менее эффективен и порождает маловероятный на практике, но всё же существующий race-condition между вызовом fincore() и соответвующим read() или sendfile(). С другой стороны fincore() нам не поможет при записи. При определенных условиях write() блокируется и хорошо бы об этом узнать заранее, чтобы отправить соответвующий таск в пул.

Милош настроен твердо: twitter.com/mtanski/status/611932316865298433
Чудо! Не могу поверить, что в самом популярном сервере поддержка пула потоков для длительных операций появилась только в 2015 году. При том, что паттерн Proactor известен науке по меньшей мере с 90-х.

Забавно, что автор винит ОС в том, что нет возможности узнать, какие данные закешированы, а какие нет.
Кешируйте сами — делов-то!

Именно так работает, например, video download сервер известной социальной сети. То, что закешировано в памяти, он отдаёт сразу из потока-селектора. А за тем, чего в кеше нет, обращается асинхронно из отдельного пула. В результате один сервер отдаёт наружу до 40 Гбит/с, причём сам сервер написан даже не на C, а на «тормозной» Java.
Не могу поверить, что в самом популярном сервере поддержка пула потоков для длительных операций появилась только в 2015 году. При том, что паттерн Proactor известен науке по меньшей мере с 90-х.
Не очень понял, какая логическая связь между двумя процитированными предложениями. Мне кажется её нет, но отвечу. Не было необходимости, да и сейчас в большинстве случаев включать пул потоков нет необходимости. Для большинства типичных задач он не нужен. И даже в тех случаях, когда он нужен, есть и другие варианты решения проблемы.

Забавно, что автор винит ОС в том, что нет возможности узнать, какие данные закешированы, а какие нет.
Кешируйте сами — делов-то!
И файловую систему пишите сами, и tcp-стек, и т.д. по списку. Как только вы начинаете кэшировать данные сами, то натыкаетесь на необходимость копировать огромные объемы данных из ядра в пользовательское пространство и обратно. Такой системный вызов, как sendfile() был создан специально для того, чтобы этим не заниматься. Вы наверное не сталкивались, когда производительность начинает упираться в память и приходится делать всё возможное, чтобы уменьшать количество копирований.

Именно так работает, например, video download сервер известной социальной сети. То, что закешировано в памяти, он отдаёт сразу из потока-селектора. А за тем, чего в кеше нет, обращается асинхронно из отдельного пула. В результате один сервер отдаёт наружу до 40 Гбит/с, причём сам сервер написан даже не на C, а на «тормозной» Java.
Безусловно есть разные подходы к решению задачи. Каждый подход имеет свои плюсы и минусы. Инженеры из известной социальной сети выбрали такой подход и мы не знаем всех факторов, которые повлияли на их решение, поэтому я не возьмусь, например, давать оценки.

Информация про 40 Гбит/с особо не говорит ни о чем, ибо число сильно далеко от того, чтобы производить впечатление в 2015 году. И если уж мериться, то нужно делить этот показатель на стоимость сервера. Одно дело раздавать 40 Гбит/c с одного сервера, а другое дело раздавать треть всего интернет трафика в США и значительную долю всего мирового трафика с серверов, собранных из недорогих комплектующих. Когда количество серверов исчесляется десятками и сотнями тысяч, то их стоимость начинает иметь существенное значение. Ну и т. д. ;)
Прошу прощения, писал ночью — грубовато получилось. Просто искренне удивился, что лежащая на поверхности идея, которую я и сам не раз упоминал в докладах про архитектуру ОК, оказывается, до недавнего времени, в NGINX не была реализована. Думал, может, какие-то подводные камни были. Ну, раз просто было не нужно — то ладно.

Ручное кеширование не отменяет sendfile. Я ж не зря ссылку на презентацию дал — там всё наглядно рассказано и показано. Создаёте один большой файл в /dev/shm, мапите в адресное пространство процесса. Работаете как с обычной памятью, разбиваете на блоки, вытесняемые по принципу LRU. А в сеть отдаёте блоки через sendfile, минуя user space.

40 Гбит/с, поверьте, у нас тоже не суперкомпьютеры раздают. Понятно, что это ближе к верхушке нашей линейки, но я про то, что это не экспериментальный стенд, а commodity оборудование, стоящее на эксплуатации.
На 2002 год, когда Игорь начал разработку nginx, он пробовал разные подходы и смотрел на треды. На тот момент с поддержкой потоков в интересующих операционных системах всё было очень и очень плохо. Поэтому да, подводные камни были.
С предложенным подходом возникает несколько вопросов. Как минимум оно не будет нормально работать на FreeBSD, поскольку там с tmpfs всё плохо в этом месте или было плохо до недавнего времени (могу уточнить этот момент у наших FreeBSD разработчиков, поскольку сам я на Linux специализируюсь).

Второй момент в том, что всё это сильно напоминает попытку реализации кэша страниц, но в пользовательском простанстве. Я вот не уверен, что это будет сильно лучше работать. Зато точно уверен, что потребует трудозатрат на более-менее универсальную реализацию и последующую поддержку. Тем более, что большинство проблем связаны как раз с тем случаем, когда горячие данные никак в память не помещаются и любое кэширование малоэффективно. Докладчик упоминает, что данные в LRU кэше в среднем живут 40 секунд. Возникает вопрос, что будете делать, когда данные там будут жить 40 микросекунд?

Решая отдельную специфическую задачу можно не наступить на ряд граблей, которые всплывут при попытке реализовать универсальное решение. Например, очень скоро обнаружится, что сильно не хватает умного readahead'а, и его тоже потребуется реализоваывать в юзерспейсе.
Решение, конечно же, должно быть опциональным и конфигурируемым. Я ведь не говорю, что нужно по умолчанию его использовать везде, включая FreeBSD, с которым, как вы утверждаете, и так всё хорошо. Или на системах, где кеш вообще бесполезен (у нас есть и такие — мы в это случае просто ставим в сервер дюжину SSD накопителей, и с кешами даже не заморачиваемся). Но в ряде случаев, как раз для раздачи тяжёлого медиаконтента с характерным профилем популярности, это и будет той серебряной пулей для Linux, о которой вы писали.

Про затраты на разработку никто не спорит. Я лишь делюсь опытом, что этот подход: а) востребован, б) относительно просто реализуем, в) приносит пользу.

Помимо прочего, кеширование в user space открывает широкое поле для различного рода улучшений. Например, вы можете явно управлять тем, какой контент как долго живёт и как вытесняется. Например, контент средней степени популярности мы при вытеснении складываем в кеш второго уровня на SSD, а непопулярный выкидываем совсем.

Дисковый же кеш очень непредсказуем. Скажем, Linux может вдруг решить, что в данный момент кеш важнее хипа приложения, и выгрузить хип в своп, несмотря на любые настройки /proc/sys/vm.

А что насчёт HTTPS? Как вам тут поможет дисковый кеш и sendfile? В нашем же случае мы без лишних копирований отправляем данные из того же кеша, заменив только sendfile на SSL_write. Конечно, 40 Гбит/с тут уже не будет, но 25 Гбит шифрованного трафика тоже немало.
Я знаю другой опыт, который подсказывает, что серебряной пулей это не будет, если вообще будет лучше. Кстати, так работает Varnish Cache например, он полностью управляет своим кэшом, но особого выигрыша там не видно.

Есть две крайности — одна запихивать всё в ядро (аля TUX), а другая реализовывать всё в юзерспейсе. Последний подход чреват тем, что в итоге всё равно прослойка в виде ядра остается и проблемы ядра никуда не уходят, а только множаться. Ядро работает с сетью, ядро работает с диском, и от этого никуда не деться, если только не выкинуть ядро совсем, но это уже другая история. Я согласен с тем, что у приложения есть больше информации о том, как лучше кэшировать данные, но это не является причиной приложению брать на себя функции ядра. Это является поводом улучшить интерфейс ядра в этом месте.

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

При этом столкнулись с проблемой вытеснения данных из кэша, до того, как отработает sendfile() и никак эту проблему не решили. Возможно это приемлемо для конкретного случая, но во всех остальных это будет неприемлемо и серьезной проблемой.

Улучшение, о котором вы говорите — скорее не улучшение, а наоборот ещё один механизм, который уже реализован ядром и его потребуется реализовывать заново. Сейчас всё это можно настроить с помощью dm-cache или bcache достаточно гибко и эффективно.

И ещё ни разу не видел, чтобы при наличии свободных страниц ядро начинало бы свопить если не преодолен рубеж swappiness. Если есть какая-то проблема в ядре, которая приводит к странностям и дергадации в определенных ситуациях, то её нужно исправлять.

В свою очередь с за'map'ленными в память файлами есть серьезная проблема. Сейчас если у нас происходит ошибка чтения с диска, то read() или sendfile() возвращает ошибку, приложение может эту ошибку обработать и продолжить работать дальше. С map() приложение упадет, что для асинхронного сервера, обрабатывающего тысячи запросов в одном процессе очень больно.

Если мы будем копировать с диска в /dev/shm, то будет дополнительная работа, во-первых. Во-вторых, возникает проблема со свопингом, если наш кэш начнет свопиться, то всё «превратиться в тыкву».

Из памяти можно отдавать столько, сколько позволяет шина памяти и сетевая подсистема, 40 Гбит/сек тут не предел. Сложности наступают тогда, когда нужно раздавать 150 терабайт с одного сервера и упираешься во все подряд так, что приходится пробовать различные подходы, работать с вендорами жестких дисков, оптимизировать прошивку, драйвера, различные подсистемы ядра и сетевой стек, изобретать новый алгоритм для congestion control в TCP. Всего этого в докладе одной социальной сети не наблюдается.

В свою очередь советую послушать:
events.yandex.ru/lib/talks/2682 — там же есть ответ на ваш вопрос о HTTPS ;-)
events.yandex.ru/lib/talks/2396
По-моему, вы сейчас пытаетесь на ходу придумать оправдания, почему кеширование в /dev/shm плохо. Не хотите так делать — не надо, кто-нибудь другой сделает. Я ж не принуждаю, а просто делюсь положительным опытом.

При этом столкнулись с проблемой вытеснения данных из кэша, до того, как отработает sendfile() и никак эту проблему не решили.
Решили тривиальным reference counter'ом.

И ещё ни разу не видел, чтобы при наличии свободных страниц ядро начинало бы свопить если не преодолен рубеж swappiness.
А я, вот, видел, как при swappiness=0 Linux вместо освобождения page cache иногда начинает свопить полезную память. В тоже время со свопом кеша из /dev/shm проблем нет. Тем более, что кеш этот — одна непрерывная область, которая легко лочится через mlock. Так что метафора про «тыкву» неуместна.

В свою очередь с за'map'ленными в память файлами есть серьезная проблема. Сейчас если у нас происходит ошибка чтения с диска
С какого диска? Мы мапим файл из tmpfs, он целиком в памяти.

Если мы будем копировать с диска в /dev/shm, то будет дополнительная работа
Серьёзно? Если данные востребованы, и мы хотим их закешировать в памяти, как это можно сделать проще, не делая read?

приходится пробовать различные подходы, работать с вендорами жестких дисков, оптимизировать прошивку, драйвера, различные подсистемы ядра и сетевой стек, изобретать новый алгоритм для congestion control в TCP
Вот, как раз про всё это вас было бы очень интересно послушать :)
По-моему, вы сейчас пытаетесь на ходу придумать оправдания, почему кеширование в /dev/shm плохо. Не хотите так делать — не надо, кто-нибудь другой сделает. Я ж не принуждаю, а просто делюсь положительным опытом.
У меня свое мнение на этот счет и свой опыт, поэтому я привожу аргументы в пользу того, что не стоит один положительный опыт обощать в качестве универсального решения.

Решение с fincore() или RWF_NONBLOCK оно универсально. Поскольку его можно включить и оно у всех будет работать, не важно сколько у человека памяти и как она используется, не важно, что у человека на том же сервере ещё и база данных крутится, php-процессы и log rotate периодически запускается сжимая большие логи. А главное, ничего отдельно настраивать не придется, не придется подбирать размер кэша.

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

Тем более, что кеш этот — одна непрерывная область, которая легко лочится через mlock. Так что метафора про «тыкву» неуместна.

Если взять и залочить огромный объем памяти, то мы автоматически лишаем этой памяти систему. В итоге в своп улетит что-нибудь другое, не менее важное. У системного кэша страниц в этом случае приемущество, что он динамический.

В вашей системе — кэш есть центр вселенной и всё вращается вокруг него, у множества пользователей nginx это не так. И завтра человек залочит 3 Гб памяти из имеющихся у него 4 на его VPS-ке, а в пик нагрузки у него php-fpm демоны улетят в своп.

Я не пытаюсь утверждать, что ваше решение плохое, я все пытаюсь донести мысль, что у него есть свои минусы и с решением одной проблемы, мы принесем множество других.

Я не исключаю, что подобный кэш будет когда-нибудь реализован в nginx для отдельных случаев, но сегодня нужны fincore() или RWF_NONBLOCK.

С какого диска? Мы мапим файл из tmpfs, он целиком в памяти.
И при этом файл пустой.

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

Я понимаю, что вы делаете иначе. Следующий абзац и был про ваш случай, на что вы вполне резонно заметили:
Серьёзно? Если данные востребованы, и мы хотим их закешировать в памяти, как это можно сделать проще, не делая read?
Всё верно, изобретая промежуточный слой в виде tmpfs, вы вынуждены делать сперва read() и копировать в него данные.

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

Вот, как раз про всё это вас было бы очень интересно послушать :)
Я ссылку и дал ниже на пару докладов Глеба Смирнова, который непосредственно работает с Netflix-ом и может гораздо больше и лучше меня об этом рассказывать.
Спутанно получилось. Третий час ночи, пора спать.

Если коротко, то:

1. Есть различные подходы.

2. Один из них — это оставить управление кэшом операционной системе, как nginx и делает. И его пример показывает, что для большинства он неплохо работает. Поэтому я склонен считать его всё же более универсальным.

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

4. У каждого из этих подходов есть приемущества и недостатки. И рассматривать их нужно в зависимости от решаемой задачи.

5. Как бы то ни было, fincore() или RWF_NONBLOCK нам с текущим подходом поможет.
Кстати, к вопросу о других решениях. NGINX мы тоже тестировали, хотели использовать в качестве прокси. Не вышло: уже при трафике в 3 Гбит/с CPU usage улетал в 100%. Деталей, к сожалению, не знаю, могу потом у коллег уточнить. Просто о неудачах редко когда рассказывают на конференциях :) Хотя, согласен, отрицательный опыт тоже порой очень интересен.
Польза большинства отрицательных опытов гораздо выше большинства положительных.
В православной ОС идут работы по переносу части SSL в ядро как раз, чтобы не копировать лишниый раз.
Мда, я так выразился, что вы, наверное, подумали, будто у нас всего один download сервер :) Не, их много. И они генерируют львиную долю трафика, пусть и не Штатов, но хотя бы Рунета.
CentOS 7 x64

$ uname -a
Linux 3.10.0-229.1.2.el7.x86_64 #1 SMP x86_64 GNU/Linux

$ nginx -V
nginx version: nginx/1.8.0
built by gcc 4.8.2 20140120 (Red Hat 4.8.2-16) (GCC) 
...
--with-file-aio


При вставке в /etc/nginx/nginx.conf строчки aio threads; получаю ошибку:

[emerg] «aio threads» is unsupported on this platform

PS: nginx из репозитория nginx.org/packages/centos/7

Что я делаю не так?
Опция --with-file-aio не имеет отношения к тредам, она про файловое AIO в ядре. NGINX должен быть собран с опцией --with-threads. В нашем репозитории на данный момент с ней собирается только mainline версия пакетов и для тех дистрибутивов, где glibc не слишком старый и в нём есть поддержка eventfd(). Попробуйте поставить 1.9.2.
>>если бы существовал эффективный способ узнать заранее, находятся ли необходимые данные в памяти или нет, и только в последнем случае выгружать операцию в отдельный поток.
То есть второй поток awk из примера всегда заново читает данные с диска, именно поэтому всего 250rps?
Всего 250?! Вы хоть раз такую нагрузку симулировали?
А именно воссоздадим наиболее тяжелые условия, заставив NGINX выполнять смесь блокирующих и неблокирующих чтений, когда проблема блокировок на обращениях к диску проявит себя в полной мере.
Вы через строчку читаете?
И пока дисковая подсистема делает свою работу как может, обслуживая наш “паразитный” трафик с первой машины, NGINX использует оставшиеся ресурсы процессора и пропускную способность сети, чтобы обслужить второго клиента из памяти.

Грубо говоря, есть огромный disk load (паразитный ли нет, есть не суть важно). Кэш вымывается, скорость чтения падает и т.д. Но из за того что имеем теперь пул потоков, а не один воркер, этот пул может отдать в 31 раз больше (т.е. целых 250 вместо всего 8 rps) например то, что еще или уже лежит в памяти.
Я так и не понял, кто следит за тем, чтобы эти вот эти небольшие данные всегда лежали в памяти и не вымавались оттуда, несмотря на огромный фоновый disk load?
Можно ли например на location с сотнями тысяч картинок включить aio threads, а для location со статическими файлами дизайна сайта, которые всегда должны быть в памяти и отдаваться максимально быстро, не включать aio threads, не теряя в этом случае скорость на помещение задания в очередь?

Nginx с aio threads теперь так же быстр например для тех же фотографий, как решение elliptics от yandex или backpack от habrahabr.ru/post/184652?
Я так и не понял, кто следит за тем, чтобы эти вот эти небольшие данные всегда лежали в памяти и не вымавались оттуда, несмотря на огромный фоновый disk load?
Этим занимается операционная система. Если к данным регулярно обращаются, то они всегда всплывают наверх очереди в кэше страниц и не успевают из него вымываться. Пулы потоков тут не причем.

Можно ли например на location с сотнями тысяч картинок включить aio threads, а для location со статическими файлами дизайна сайта, которые всегда должны быть в памяти и отдаваться максимально быстро, не включать aio threads, не теряя в этом случае скорость на помещение задания в очередь?
Можно.
250rps по 4 мегабайта каждый — это чуть менее 1 гигабайта в секунду или ~8.4 гигабит в секунду. Поскольку данные запрашивались по 10 гигабитной сети с двух клиентов, то это просто та эффективная полоса, которая осталась свободной для запросов со второй машины, создающей тестовую нагрузку к одному файлу. Как я и написал в статье, в данном случае nginx уперся в максимальную пропускную способность сети на тестовом стенде.

Именно поэтому 250rps.

Как явно было указано, wrk со второй машины всегда получает данные из памяти. Благодаря этому он разогнался до ~8.4 гигабит и загрузил сеть по полной.

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

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

В случае, когда данные находятся в памяти, отправляя задание в поток nginx тратит больше ресурсов, чем если бы он выполнил чтение и отправку в рамках основного процесса. В данном тесте эти траты ничтожны по сравнению с выигрышем от избавления от блокировок на чтении с диска.
Есть ли смысл в установке ненулевого значения sendfile_max_chunks при использовании пула потоков?
До пула потоков sendfile мог заблокировать рабочий процесс, но с потоками этой проблемы уже нет, не так ли?

Всё так. Смысла особого нет.

Подумал ещё и понял, что смысл всё-таки есть. Если sendfile() подолгу блокируется, то может возникнуть ситуация, когда все потоки из пула потоков окажутся заняты такими долгими вызовами, а в очереди на обработку будут накапливаться запросы от других клиентов. Чтобы все они как-то ротировались, сменяя друг друга, а не отваливались по таймауту, и может пригодится sendfile_max_chunks

При включённом в nginx'e aio_threads — системный Load Average вырастает на порядок и перестаёт быть объективным показателем, потому что даже при LA > 10 система остаётся отзывчивой и быстрой.

Какие параметры посоветуете использовать вместо LA для контроля состояния системы?
Sign up to leave a comment.

Articles