Еще в мае этого года я посетил 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 . Обратите внимание, что задержка отображается зеленым цветом на логарифмической оси. Пропускная способность выделена синим цветом на линейной оси (диапазоны для диаграмм разные).
Мы наблюдаем увеличение количества ошибок при выходе за пределы 25 тыс. Потоков. Поскольку генератор нагрузки находится в тесной связи с тестируемой системой, похоже, именно в этот момент собственная память JMeter и потребление ресурсов ЦП слишком сильно влияют на тестируемую систему — поэтому мы будем игнорировать диапазон выше 25 КБ.
Наиболее интересным является тот факт, что в Ratpack задержка остается почти постоянной в диапазоне потоков [2,5–10k], тогда как стандартный сервер Neo4j демонстрирует увеличение задержки.
В потоках 2.5k Ratpack показывает полностью насыщенный процессор, поэтому пропускная способность уменьшается. С более или более быстрым процессором мы могли бы улучшить как задержку, так и пропускную способность. Объяснение наблюдаемой разницы можно найти в другой модели потоков. Сервер Neo4j использует Jetty внутри, что блокирует ввод-вывод, а Ratpack использует Netty. Чтобы убедиться в этом, я взял дампы потоков с Yourkit:
Интересно видеть, что сервер Neo4j использует 10 рабочих потоков на ядро (всего 40 на моем ноутбуке). Большую часть времени большинство из них находятся в заблокированном состоянии, обозначенном красным цветом. Ratpack, с другой стороны, имеет 8 рабочих потоков, которые в основном находятся в «зеленом» состоянии, то есть в рабочем состоянии. Таким образом, Ratpack действительно использует неблокирующий ввод-вывод.
Вывод
Для случаев использования только Cypher с высокими требованиями к параллелизму, использование Ratpack вместо сервера Neo4j может быть интересной альтернативой. Однако следует помнить, что Ratpack — это новейшая версия, текущая версия 0.9-SNAPSHOT.