Статьи

JVM Инструментарий «Ratpack» и Neo4j

Еще в мае этого года я посетил Gr8conf в Копенгагене. Как всегда, эта конференция добавила несколько вещей в мой личный список «взгляни на это». Самым захватывающим для меня был Ratpack , простой набор инструментов для создания веб-приложений на JVM. Ratpack работает на Netty и предоставляет управляемый событиями сетевой механизм, в отличие от классических контейнеров на основе сервлетов, таких как Tomcat или Jetty, которые связывают потоки с запросами. В сценариях с высокой нагрузкой и огромным количеством одновременных запросов модель, основанная на потоках, страдает от блокирования потоков, когда Ratpack практически не блокируется. Чтобы ознакомиться с Ratpack, я решил реализовать серверный компонент для Neo4j на основе Ratpack. Моей первой целью было получить конечную точку Cypher, как и стандартные предложения Neo4j, Вторичные цели были еще несколько функций:

  • Поддержка нескольких форматов вывода: JSON, HTML, CSV,  пакет сообщений
  • Возможность получить список текущих запущенных запросов и кнопку для отмены каждого из них в отдельности. Это IMHO особенность, которой нет в классическом сервере Neo4j. Люди, начинающие работать с Cypher, обычно пишут запросы, которые выполняются очень долго, и теперь есть способ их прервать.

В будущем я хотел бы добавить еще несколько функций:

  • Транзакционная конечная точка Cypher
  • TBD (если у вас есть идеи, пожалуйста, отправьте комментарий)

Цель состоит не в том, чтобы создать полноценную альтернативу существующему серверу Neo4j . Этот проект должен быть ориентирован на максимальную пропускную способность и простоту использования для серверного компонента только для Cypher. Для начала я клонировал  https://github.com/ratpack/example-ratpack-gradle-groovy-app . Вы найдете мой код по адресу  https://github.com/sarmbruster/neo4j-ratpack .

Обработка запросов

В Ratpack вы либо пишете встроенные обработчики в src / ratpack / ratpack.groovy, либо, для более сложных случаев, пишете класс обработчика, производный от AbstractHandler, и регистрируете его в ratpack.groovy .

Ratpack также поддерживает Google Guice, поэтому мы можем зарегистрировать, например, GraphDatabaseService в качестве инъецируемого компонента. Смотрите Neo4jModule . Мы представляем и настраиваем GraphDatabaseService , Cypher ExecutionEngine , guard (см. Ниже) и QueryRegistry . Мы можем ссылаться на другие компоненты, используя аннотацию конструктора @Inject .

Основной кусок кода — CypherHandler . Он анализирует команду и параметры Cypher из запроса, запускает ее и отображает результат в зависимости от запрошенного типа контента.

Завершить запросы

С технической точки зрения это была самая интересная часть для написания. Neo4j можно запустить с дополнительной защитой. Поскольку эта функция не является частью общедоступного API, она официально не задокументирована и поэтому может быть изменена без дополнительного уведомления — будьте осторожны. Чтобы включить функцию защиты, execution_guard_enabledнеобходимо настроить параметр конфигурации на true. Тем не менее, вы можете получить доступ к охраннику , позвонив 

((GraphDatabaseAPI)graphDb).dependencyResolver.resolveDependency(Guard.class)

В neo4j-ratpack защита выставляется как компонент Guice , поэтому любой обработчик ratpack может просто внедрить его.

Каждый запрос регистрируется с помощью QueryRegistry . Частью этого процесса является настройка VetoGuard, которая выдает исключение на основе логического флага. В случае исключения запрос прерывается.

Нагрузочные тесты

Следующим шагом было выполнение некоторых нагрузочных тестов для стандартного сервера Neo4j и neo4j-ratpack для сравнения производительности компонентов сервера. Все тесты проводились на моем ThinkPad x230 (i7-3520M, 2,9 ГГц, 16 ГБ ОЗУ, Ubuntu 13.04). Для простоты генерация нагрузки и сам сервер работали на одной и той же машине, что далеко не идеально, но является отправной точкой.

Целью этих нагрузочных тестов не является измерение самого Neo4j — он сосредоточен только на компоненте сервера.

Используя JMeter. Я запустил запрос Cypher …

START person=node:person(firstName={firstName}) 
WITH person 
ORDER BY person.lastName LIMIT 10 
MATCH (uniCity)<-[:IS_LOCATED_IN]-(uni)<-[studyAt:STUDY_AT]-(person), 
    (company)<-[worksAt:WORKS_AT]-(person)-[:IS_LOCATED_IN]->(personCity), 
    (company)-[:IS_LOCATED_IN]->(companyCountry) 
RETURN person.firstName, person.lastName, person.birthday, person.creationDate, person.gender, person.browserUsed, person.locationIP, personCity.name, uni.name, studyAt.classYear, uniCity.name, company.name, worksAt.workFrom,companyCountry.name

… с различными параметрами для базы данных графа, состоящей из 1.6M узлов, 7M отношений и 7M свойств. Престижность моему коллеге Алексу,  который помог мне настроить набор данных на основе проекта LDBC, с которым он связан.

Один и тот же файл graph.db использовался как сервером Neo4j, так и neo4j-ratpack . Никаких конкретных параметров настройки JVM не было установлено. Я запустил нагрузочное тестирование с увеличением числа одновременно работающих потоков и сосредоточился на наблюдении за пропускной способностью и задержкой. Следующие диаграммы были созданы с использованием сценария Python matplot, начиная с  http://www.metaltoad.com/blog/plotting-your-load-test-jmeter . Обратите внимание, что задержка отображается зеленым цветом на логарифмической оси. Пропускная способность выделена синим цветом на линейной оси (диапазоны для диаграмм разные).

neo4jserver_jdk7

,
ratpack_jdk7

Мы наблюдаем увеличение количества ошибок при выходе за пределы 25 тыс. Потоков. Поскольку генератор нагрузки находится в тесной связи с тестируемой системой, похоже, именно в этот момент собственная память JMeter и потребление ресурсов ЦП слишком сильно влияют на тестируемую систему — поэтому мы будем игнорировать диапазон выше 25 КБ.

Наиболее интересным является тот факт, что в Ratpack задержка остается почти постоянной в диапазоне потоков [2,5–10k], тогда как стандартный сервер Neo4j демонстрирует увеличение задержки.

В потоках 2.5k Ratpack показывает полностью насыщенный процессор, поэтому пропускная способность уменьшается. С более или более быстрым процессором мы могли бы улучшить как задержку, так и пропускную способность. Объяснение наблюдаемой разницы можно найти в другой модели потоков. Сервер Neo4j использует Jetty внутри, что блокирует ввод-вывод, а Ratpack использует Netty. Чтобы убедиться в этом, я взял дампы потоков с Yourkit:

потоковая телеметрия сервера neo4j

потоковая телеметрия сервера neo4j

потоковая телеметрия neo4j-ratpack

Потоковая телеметрия neo4j-ratpack

Интересно видеть, что сервер Neo4j использует 10 рабочих потоков на ядро ​​(всего 40 на моем ноутбуке). Большую часть времени большинство из них находятся в заблокированном состоянии, обозначенном красным цветом. Ratpack, с другой стороны, имеет 8 рабочих потоков, которые в основном находятся в «зеленом» состоянии, то есть в рабочем состоянии. Таким образом, Ratpack действительно использует неблокирующий ввод-вывод.

Вывод

Для случаев использования только Cypher с высокими требованиями к параллелизму, использование Ratpack вместо сервера Neo4j может быть интересной альтернативой. Однако следует помнить, что Ratpack — это новейшая версия, текущая версия 0.9-SNAPSHOT.