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

Одна из компаний, в с которой я работал my-Channels — поставщик сервисов обработки сообщений. Они действительно крутые и дали свое благословление на публикацию в блоге про некоторые интересные вещи, над которыми я работал для них.



Для справки, my-Channels — поставщик сервисов обработки сообщений, который специализируется на доставке данных для каждого устройства известного человеку через сети, такие как интернет или ваша корпоративная сеть. Они могут доставлять текущие финансовые данные на ваш компьютер, ноутбук дома или ваш iPhone на максимально возможной скорости. В последнее время они сделали стратегический шаг, чтобы войти в нишу low-latency обмена сообщениями предприятия, отчасти из-за этого им потребовались мои услуги. Они хотят перейти к low-latency без отказа от богатой функциональности, которую предлагает их продукт, что дало мне некоторые интересные вызовы к решению.

Просто насколько плохи задержки (latency) в подобных продуктах, когда входят в пространство low-latency решений? Я не имел высоких ожиданий, потому что, по честному, это никогда не было их целью. После некоторых начальных тестов, я подумал что эти ребята не в плохом состоянии.

Хорошо, достаточно основных тестов, теперь настало время серьезных. Я работал с командой, чтобы создать подходящие нагрузочные тесты и получить результат работы профайлеров. Без значительных неожиданностей тут — когда запустили нагрузку, борьба за блокировки(lock-contention) становился главной причиной, ограничивающей и латентность и пропускную способность. Идя вниз по списку, обнаружим множество других интересных вещей, но давайте следовать хорошей дисциплине и начнем с верхней части списка.

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

Таким образом, чтобы решить эту проблему, мы разработали новый неблокирующий (lock-free) Executor, чтобы заменить стандартную Java реализацию. Тесты показали, что новый executor приблизительно в 10 раз лучше чем то что предлагает JDK. Мы интегрировали новый Executor в код проекта и теперь бутылочное горлышко пропускной способности приложения значительно изменилось. Система теперь справляется с пропускной способностью в 16 раз больше и гистограмма латентности стала более сжатой. Это хороший пример того, как макро бенчмарк гораздо более ценен, чем микро бенчмарк. Мы подумали — неплохое начало.

Мы тестировали продукт на всех значимых JVM и наиболее предсказуемые значения задержек были достигнуты с Azul Zing. Zing был с самым лучшим профилем латентности, практически без длинного хвоста. Для многих из тестов он также давал лучшую пропускную способность.

После того как проблема с борьбой за блокировки на Executor была решена, следующее бутылочное горлышко, когда нагрузочные тесты на той же машине ограничивались использованием TCP между процессами через loopback адаптер. Мы обсудили разработку нового транспорта для Nirvana, который не был бы основан на сетевом взаимодействии. Для этого мы решили применить множество техник, которые я учил на курсах lock-free concurrency. Это привело в результате к новому виду транспорта межпроцессного взаимодействия (IPC), основанному на разделяемой памяти (shared memory) реализованном через memory-mapped файлы в Java. Мы выполнили межсерверное тестирование используя 10GigE сеть, и было интересно использовать сетевые адаптеры Solarflare с OpenOnload, но в этой статье буду придерживаться рассказа про Java.

Разработка IPC транспорта раскопала множество челленджей с разными JVM реализациями MappedByteBuffer. После нескольких полезных чатов с Cliff Click и Doug Lea мы пришли к решению, которое работает на всех JVM. Это решение показывает среднюю задержку ~100ns на лучших JVM и может обеспечивать пропускную способность ~12-22 миллионов сообщений в секунду для 60-байтных сообщений, в зависимости от JVM. Это был первый раз, когда мы обнаружили тест, где Azul не был близким, чтобы быть самым быстрым. Я изолировал тест и послал им его в пятницу.

В воскресенье вечером я получил письмо от Gil Tene, где он говорит, что определил проблему и во вторник Cliff Click сделал исправление, которое мы проверили на следующей неделе. Когда мы тестировали новую Azul JVM, мы увидели более 40 миллионов сообщений в секунду при латентности около 100ns для нашего нового IPC транспорта. Я дразнил Azul что это должно быть возможно в Java, потому что я создал подобный алгоритм на C и ассемблере, чтобы показать что платформа x86_64 способна на это.

Я начинаю заговариваться, но у нас была веселуха с удалением задержек во многих частях стека. Когда у меня будет больше времени, я напишу заметку о других находках. Текущее состояние все еще в работе, с ежедневным прогрессом в удивительном масштабе. Ребята в my-Channels очень консервативны и не хотят публиковать текущие цифры до тех пор, пока не выйдет общедоступная версия 7.0 Nirvana и пока не проведут более всестороннее тестирование. На данный момент мы счастливы раскрыть вам следующее:
  • Пропускная способность увеличилась в 32 раза, по причине реализации неблокирующих техник и оптимизации стека вызова для обработки сообщений, чтобы убрать любые разделяемые зависимости.
  • Средняя задержка уменьшилась в 20 раз, применяя те же самые техники, и мы определили множество других возможных улучшений.
  • Мы знаем, что «сырой» транспорт для IPC теперь ~100ns и в худшем случае задержка из-за GC 80µs c Azul Zing. Так как эта задержка для double hop между продюсером и консьюмером через IPC, через брокер, я оставлю вашему воображению как где-то между этими цифрами. До тех пор пока парни не готовы сделать официальное заявление. Как вы можете предположить, это гораздо меньше чем 80µs


Для меня было большой неожиданностью, что задержки GC занимают только 80µs в худшем случае. Глядя на планировщик OS в отдельности, наблюдаемый результат с большим разбросом (jitter). Я обсуждал это с Gil Tene из Azul, и даже он был удивлен. Он ожидал некоторые сценарии в худшем случае с их JVM будут 1-2ms для хорошо работающего приложения. Затем мы исследовали настройку my-Channels, и оказывается, что мы сделали все почти идеально, чтобы получить лучшее из JVM, чем стоит поделиться:

  1. Не используйте блокировки в главном потоке транзакции, потому что они вызывают переключение контекста, а следовательно задержки и непредсказуемый джитер.
  2. Никогда не выделяйте больше потоков которые необходимо запустить, чем у вас есть свободных ядер процессора.
  3. Привяжите поток к конкретному ядру (affinity of threads), чтобы избежать cache pollution за счет миграции между ядрами. Это особенно важно для серверов с множеством сокетов, по причине NUMA эффекта
  4. Убедитесь в несоревновательном доступе к любому ресурсу, соблюдая принцип одного писателя, так, чтобы biased locking мог быть вашим другом.
  5. Поддерживайте стек вызова относительно небольшим. Все еще много работы, чтобы это сделать. Если вы достаточно чокнуты, чтобы использовать Spring (прим. переводчика: зависит от задачи), проверьте стек вызова чтобы увидеть что это значит. Сборщик мусора обходит его, чтобы найти достижимые объекты
  6. Не используйте finalizers.
  7. Поддерживайте генерацию мусора на сдержанном уровне. Это применимо к большинству JVM, но не является проблемой для Zing.
  8. Убедитесь в отсутствии дискового ввода/вывода в главном потоке
  9. Сделайте правильный «прогрев» (warm-up) перед началом измерения.
  10. Сделайте соответствующую настройку ОС для low-latency системы, обзор, как это делать, за пределами этой статьи. Например, выключите C-States управления питания в BIOS и наблюдайте, как RHEL 6 включает его обратно, не предупреждая вас. (прим. переводчика забавно, не слышал про это, и у RHEL отличная perf tuning документация, видимо «фича»)


Следует заметить, что мы столкнулись с этим на некоторых современных процессорах Intel с очень большим L3 кешем. Возможно получить 20-30MB L3 кеш на одном сокете в наши дни. Вполне вероятно, что все наше приложение выполнялось из кеш памяти L3, за исключением потока сообщений, который очень предсказуем.

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

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

И my-Channels и Azul показали во время этого взаимодействия, что возможно сделать, когда «s*#t happen».
Это было абсолютный прорыв, работать с людьми, которые так быстро усваивают информацию и идеи и вкручивают их в работающее ПО. Для этого я буду смущать Matt Buckton из my-Channels, Gil Tene и Cliff Click из Azul, кто никогда не проигрывают при возникающих челленджах. Так мало организаций, которые могут сделать столь большой прогресс за столь короткий промежуток времени. Если вы думаете, что Java не может работать в высокопроизводительных системах, поработайте с одной из этих двух компаний и вы будете думать иначе. Бьюсь об заклад, несколько месяцев назад Matt никогда бы не подумал, что он будет сидеть в аэропорту Сингапура, писать свою первую в жизни реализацию multi-producer неблокирующей очереди во время путешествия домой, и он действительно будет наслаждаться этим!

Замечания по переводу, пожалуйста, в ЛС

Комментарии (4)


  1. 23derevo
    21.10.2015 13:54

    Клиф Клик уже три с половиной года не работает в Azul.

    Статье — тоже три с половиной года.

    В чем смысл статьи — в рекламе Azul?


    1. igor_suhorukov
      21.10.2015 13:59

      Привет Алексей!
      Ты прав, но в статье рассмотренны техники, которые полезны и сегодня при оптимизации. Я с Azul никак не связан да и точно не рекламирую их дорогое и «нишевое» решение


      1. 23derevo
        21.10.2015 14:06

        я вижу список некоторых шагов, некоторые из которых в России в 2015ом году выглядят капитанскими (например, thread affinity и warmup для бенчмарков), а некоторые — откровенно спорными (количество потоков меньше количества ядер. Да ну, а если ядра недогружены?)

        Остальное — вообще реклама какая-то.


        1. igor_suhorukov
          21.10.2015 14:16

          в России в 2015ом некоторые проекты только собираются на java 8 переходить, может кому-нибудь и будет полезно. Я не претендую на новизну, поскольку как ты сам сказал про возраст статьи.

          Nirvana тоже достаточно нишевое решение, но я видел своими глазами удачный пример ее внедрения и где она до сих пор конкурирует с программно-аппаратным решением от Solace Systems.

          По последнему замечанию, повторюсь что это не моя статья, так что выбросить названия my-channels, поглощенных Software AG и Azul я тоже не могу.