Статьи

Предоставление Neo4j 2.2 тренировки

rhino_running

Neo4j 2.2

выпускается в любой день
[недавно был выпущен], так что давайте попробуем Release Candidate с Gatling . Как только мы загрузим и запустим его, вы заметите, что он хочет, чтобы мы аутентифицировались.

Аутентифицировать

Имя пользователя и пароль по умолчанию — neo4j / neo4j. Мы будем использовать это, и он попросит нас изменить его на что-то более безопасное. Поэтому дайте мне минуту или две здесь, чтобы придумать что-то оригинальное .

SwordfishBRCLTv1

Ладно, я понял. Теперь, когда мы соединились, давайте создадим набор данных. Мы собираемся создать случайную социальную сеть, как в прошлом . Итак, сначала мы создадим пользователей:

WITH ["Jennifer","Michelle","Tanya","Julie","Christie","Sophie","Amanda","Khloe","Sarah","Kaylee"] AS names 
FOREACH (r IN range(0,100000) | CREATE (:User {username:names[r % size(names)]+r}))

и подключить их:

MATCH (u1:User),(u2:User)
WITH u1,u2
LIMIT 5000000
WHERE rand() < 0.1
CREATE (u1)-[:FRIENDS]->(u2);

Давайте попробуем запустить запрос. Какие пользователи дружат с «Kaylee83639 ″?

MATCH (me:User {username:'Kaylee83639'})-[:FRIENDS]-(people)
RETURN people.username

Снимок экрана 2015-03-06 в 16.08.21

Отлично, это работает. Теперь давайте выключим браузер и запустим IntelliJ . Мы создадим новый проект из Maven Archetype . Мы будем использовать Gatling для тестирования нашего шифровального запроса, но мы сделаем это непосредственно в нашей IDE, а не на консоли.

Снимок экрана 2015-03-06 в 15:00

Мы используем «io.gatling.highcharts» для GroupId, «gatling-highcharts-maven-archetype» для ArtifactId и «2.1.2» для версии. Задайте ему несколько настроек, затем подождите несколько секунд, пока он создаст ваш проект. Далее мы создадим симуляцию, назовем ее «GetFriends» и настроим ее для подключения к нашему локальному хосту через порт 7474.

import io.gatling.core.Predef._
import io.gatling.core.scenario.Simulation
import io.gatling.http.Predef._

import scala.concurrent.duration._

class GetFriends extends Simulation {

  val httpConf = http
    .baseURL("http://localhost:7474")
    .acceptHeader("application/json")
    /* Uncomment to see the response of each request.
    .extraInfoExtractor(extraInfo => {
      println(extraInfo.response.body.string)
      Nil
    }).disableResponseChunksDiscarding
   */

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

val query = """MATCH (me:User {username:'Kaylee83639'})-[:FRIENDS]-(people) RETURN people.username"""
val cypherQuery = """{"statements" : [{"statement" : "%s"}]}""".format(query)

Теперь мы можем настроить наш сценарий, чтобы выполнить запрос 1000 раз для каждого пользователя Гатлинга. Мы передадим наш запрос на шифрование как тело сообщения JSON в конечную точку шифра и убедимся, что получим ответ «ОК» от сервера.

  val scn = scenario("Get Friends")
    .repeat(1000) {
    exec(
      http("get friends")
        .post("/db/data/transaction/commit")
        .body(StringBody(cypherQuery))
        .asJSON
        .check(status.is(200))
    )
  }

Далее мы настроим наш сценарий так, чтобы он начинался с 1 пользователя и увеличивал до 10 пользователей в течение 10 секунд.

  setUp(
    scn.inject(rampUsers(10) over(10 seconds)).protocols(httpConf)
  )

… и теперь мы можем запустить его. Щелкните правой кнопкой мыши на файле «Engine» в нашем каталоге scala и запустите его.

Снимок экрана 2015-03-06 в 3.39.07 вечера

Следуйте инструкциям (или просто дважды нажмите Enter) и посмотрим, что произойдет:

================================================================================
---- Global Information --------------------------------------------------------
> request count                                      10000 (OK=0      KO=10000 )
> min response time                                      0 (OK=-      KO=0     )
> max response time                                      4 (OK=-      KO=4     )
> mean response time                                     0 (OK=-      KO=0     )
> std deviation                                          0 (OK=-      KO=0     )
> response time 50th percentile                          0 (OK=-      KO=0     )
> response time 75th percentile                          1 (OK=-      KO=1     )
> mean requests/sec                                1047.559 (OK=-      KO=1047.559)
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                             0 (  0%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                             10000 (100%)
---- Errors --------------------------------------------------------------------
> status.find.is(200), but actually found 401                     10000 (100.0%)
================================================================================

Вау! Все они потерпели неудачу. Но мы можем легко сказать почему. Он ожидал статус 200 и вместо этого получил 401. Если вы помните свои коды состояния http , 401 означает «Несанкционированный». Нам нужно изменить наш тест, чтобы передать имя пользователя и пароль, которые мы создали ранее.

val scn = scenario("Get Friends")
    .repeat(1000) {
    exec(
      http("get friends")
        .post("/db/data/transaction/commit")
        .basicAuth("neo4j", "swordfish")
        .body(StringBody(cypherQuery))
        .asJSON
        .check(status.is(200))
    )
  }

Теперь это работает. Я проведу тест пару раз, чтобы все было хорошо и разогрето, и мы получаем около 32 запросов в секунду.

Снимок экрана 2015-03-06 в 4,22.08 вечера

Это не выглядит правильно, давайте посмотрим, что происходит. Возвращаясь к Neo4j, мы добавим слово «ПРОФИЛЬ» в начало нашего запроса …

PROFILE MATCH (n:User {username:'Kaylee83639'})-[:FRIENDS]-(people) RETURN people.username

… И Neo4j даст нам визуализацию того, что происходит.

Снимок экрана 2015-03-06 в 4.18.56 вечера

Посмотрите на это. Он сканирует 100 000 пользовательских узлов, ища свойство имени пользователя, равное «Kaylee83639». Это происходит потому, что мы создали наш набор данных, но забыли добавить индексы! Итак, давайте сделаем это сейчас:

CREATE INDEX ON :User(username)

Подожди, подожди, давай не будем этого делать. В этом случае свойство username должно быть уникальным для всех наших пользовательских узлов. Давайте вместо этого создадим ограничение уникальности, которое будет гарантировать, что в нашем графе существует только один Kaylee83639, а также создадим для нас индекс.

CREATE CONSTRAINT ON (me:User) ASSERT me.username IS UNIQUE

Мы можем проверить это, запустив: schema в нашей веб-консоли.

Снимок экрана 2015-03-06 в 20.32.22

Давайте попробуем наш профиль снова.

Снимок экрана 2015-03-06 в 8.33.31 вечера

Это выглядит лучше. Теперь он использует индекс пользователя (имя пользователя), чтобы найти «Kaylee83639» вместо сканирования свойств имени пользователя всех узлов пользователя. Да, я сказал ВСЕ пользовательские узлы, потому что он не знал, что существует только один Kaylee83639. Итак, вернемся к Гатлингу, что произойдет, если мы попробуем наш тест снова?

Снимок экрана 2015-03-06 в 16:5.29

Около 1035 запросов в секунду. Это на 1000 запросов в секунду больше, чем раньше, и значительное улучшение. Поэтому, пожалуйста, если вы начинаете с Neo4j, не забудьте добавить ограничения уникальности или индексы схемы в любые свойства, которые будут использоваться в качестве отправной точки для ваших обходов.

Теперь мы знаем, как быстро мы можем получить друзей Kaylee83639, но как насчет других пользователей в нашей базе данных? Давайте изменим наш тест производительности, чтобы опрашивать разных людей. Сначала нам понадобится образец, скажем, 1000 пользователей.

MATCH (n:User) RETURN n.username AS username ORDER BY rand() limit 1000

Давайте экспортируем и сохраним это как файл CSV.

Снимок экрана 2015-03-06 в 7.47.47 PM

Мы поместим этот файл в наш проект в каталог src / test / resources / data как usernames.csv и изменим наш код для ссылки на него.

val feeder = csv("usernames.csv").circular

Затем мы изменим наш запрос на шифрование, чтобы использовать параметр.

val query = """MATCH (me:User {username:{username}})-[:FRIENDS]-(people) RETURN people.username"""
val cypherQuery = """{"statements" : [{"statement" : "%s", "parameters" : { "username": "${username}" }}]}""".format(query)

Возможно, вам придется прокрутить приведенный выше код вправо, но вы заметите этот маленький самородок:

"${username}"

Это магия Гатлинга (технически это атрибут сеанса, изменяемый языком выражений Гатлинга, который автоматически анализирует его и динамически изменяет его значение). Он заменит это $ {username} значением username из файла usernames.csv, если мы передадим ему это значение при выполнении наших тестов. Поэтому мы изменим сценарий, чтобы использовать канал:

  val scn = scenario("Get Friends")
    .repeat(1000) {
    feed(feeder)
    .exec(
      http("get friends")
      ...

Наконец мы снова запустим тест.

Снимок экрана 2015-03-06 в 7.59.43 PM

Неудивительно, что получение примерно одного пользователя занимает примерно столько же времени, потому что операция та же самая, найдите узел в индексе схемы User (имя пользователя) и проследуйте оттуда. Независимо от того, есть ли у вас 100 тыс. Пользователей, 10 млн. Пользователей 1B, поиск друзей займет примерно столько же времени.

Так что используйте новые возможности PROFILE браузера Neo4j, чтобы лучше понять, что делает этот зашифрованный запрос. Найдите NodeByLabelScans, которые можно заменить на NodeUniqueIndexSeeks или NodeIndexSeeks, добавив уникальные ограничения или индексы там, где они вам нужны. И наконец, убедитесь, что вы всегда проверяете все, прежде чем отправиться в производство. Если вы следили за моим блогом, вы знаете, что всегда есть способы заставить Neo4j работать быстрее. Если вы не видите нужного вам спектакля, обязательно дайте мне знать .

Как всегда, код на GitHub .