Статьи

Neo4j и Гатлинг, сидя на дереве, выступление TEST-ING

neo4j_loves_gatling

Я познакомился с инструментом тестирования производительности с открытым исходным кодом Gatling несколько месяцев назад от Дастина Барнса и влюбился в него. Он имеет простой в использовании DSL, и хотя я не знаю, что такое Scala , я смог понять, как его использовать. Он создает довольно классную графику и выполняет за вас большую работу за кулисами. У них есть отличная документация и довольно активная группа Google, где приветствуются новички и вопросы.

Он поставляется вместе со Scala, поэтому все, что вам нужно сделать, это создать свои тесты и использовать командную строку для их выполнения. Я покажу вам, как сделать несколько основных вещей, например, проверить, что у вас все работает, затем мы создадим узлы и отношения и затем запросим эти узлы.

Мы начнем с операторов импорта:

import com.excilys.ebi.gatling.core.Predef._
import com.excilys.ebi.gatling.http.Predef._
import akka.util.duration._
import bootstrap._

Тогда мы начнем сразу с нашей симуляции. Для этого первого теста мы просто собираемся получить корневой узел через API REST. Мы указываем наш сервер Neo4j, в этом случае я тестирую на локальном хосте (вы захотите запустить тестовый код и сервер Neo4j на разных серверах, когда делаете это по-настоящему). Далее мы указываем, что мы принимаем JSON для возврата. В нашем тестовом сценарии в течение 10 секунд мы получим «/ db / data / node / 0» и проверим, что Neo4j возвращает код состояния http 200 (все будет в порядке). Мы сделаем паузу между 0 и 5 миллисекундами между вызовами, чтобы симулировать реальных пользователей, и в нашей настройке мы укажем, что нам нужно 100 пользователей.

class GetRoot extends Simulation {
  val httpConf = httpConfig
    .baseURL("http://localhost:7474")
    .acceptHeader("application/json")

  val scn = scenario("Get Root")
   .during(10) {
     exec(
       http("get root node")
         .get("/db/data/node/0")
         .check(status.is(200)))
     .pause(0 milliseconds, 5 milliseconds)
   }

  setUp(
    scn.users(100).protocolConfig(httpConf)
  )
}

Мы назовем этот файл «GetRoot.scala» и поместим его в user-files / simulations / neo4j.

gatling-charts-highcharts-1.4.0/user-files/simulations/neo4j/

Мы можем запустить наш код с:

~$ bin/gatling.sh

Мы получим подсказку с вопросом, какой тест мы хотим запустить:

GATLING_HOME is set to /Users/maxdemarzi/Projects/gatling-charts-highcharts-1.4.0
Choose a simulation number:
     [0] GetRoot
     [1] advanced.AdvancedExampleSimulation
     [2] basic.BasicExampleSimulation

Выберите номер рядом с GetRoot и нажмите ввод.

Затем вам будет предложено ввести идентификатор, или вы можете просто перейти по умолчанию, нажав клавишу ввода еще раз:

Select simulation id (default is 'getroot'). Accepted characters are a-z, A-Z, 0-9, - and _

Если вы хотите добавить описание, вы можете:

Select run description (optional)

Наконец это начинается по-настоящему:

================================================================================
2013-02-14 17:18:03                                                  10s elapsed
---- Get Root ------------------------------------------------------------------
Users  : [#################################################################]100%
          waiting:0     / running:0     / done:100  
---- Requests ------------------------------------------------------------------
> get root node                                              OK=58457  KO=0     
================================================================================

Simulation finished.
Simulation successful.
Generating reports...
Reports generated in 0s.
Please open the following file : /Users/maxdemarzi/Projects/gatling-charts-highcharts-1.4.0/results/getroot-20130214171753/index.html

Индикатор выполнения — это показатель общего числа пользователей, выполнивших свою задачу, а не показатель выполненного моделирования, поэтому не беспокойтесь, если он долгое время остается на нуле, а затем быстро переходит на 100%. Вы также можете увидеть номера OK (тест пройден) и KO (тест не пройден). Наконец, он создает отличный отчет на основе HTML для нас. Давайте взглянем:

Гатлинга

Здесь вы можете увидеть статистику о времени ответа, а также количество запросов в секунду. Это здорово, мы можем получить корневой узел, но это не очень интересно, давайте создадим несколько узлов:

class CreateNodes extends Simulation {
  val httpConf = httpConfig
    .baseURL("http://localhost:7474")
    .acceptHeader("application/json")

  val createNode = """{"query": "create me"}"""

  val scn = scenario("Create Nodes")
    .repeat(1000) {
    exec(
      http("create node")
        .post("/db/data/cypher")
        .body(createNode)
        .asJSON
        .check(status.is(200)))
      .pause(0 milliseconds, 5 milliseconds)
  }


  setUp(
    scn.users(100).ramp(10).protocolConfig(httpConf)
  )
}

В этом случае мы устанавливаем 100 пользователей для создания 1000 узлов каждый со временем линейного изменения 10 секунд. Мы запустим эту симуляцию, как и раньше, но выберите «Создать узлы». Как только это будет сделано, взгляните на отчет и немного прокрутите вниз, чтобы увидеть график количества запросов в секунду:

Снимок экрана 2013-02-14 в 5.33.29 вечера

Вы можете видеть количество пользователей, увеличивающихся за первые 10 секунд и исчезающих в конце. Давайте продолжим и соединим некоторые из этих узлов вместе:

Мы добавим JSONObject в операторы импорта, и, поскольку я хочу увидеть, какие узлы мы связываем с какими узлами вместе, мы распечатаем детали для запроса. Я случайно выбираю два идентификатора и передаю их в запрос на шифрование для создания отношений:

import com.excilys.ebi.gatling.core.Predef._
import com.excilys.ebi.gatling.http.Predef._
import akka.util.duration._
import bootstrap._
import util.parsing.json.JSONObject


class CreateRelationships extends Simulation {
  val httpConf = httpConfig
    .baseURL("http://localhost:7474")
    .acceptHeader("application/json")
    .requestInfoExtractor(request => {
      println(request.getStringData)
      Nil
    })


  val rnd = new scala.util.Random
  val chooseRandomNodes = exec((session) => {
    session.setAttribute("params", JSONObject(Map("id1" -> rnd.nextInt(100000),
                                                  "id2" -> rnd.nextInt(100000))).toString())
  })

  val createRelationship = """START node1=node({id1}), node2=node({id2}) CREATE UNIQUE node1-[:KNOWS]->node2"""
  val cypherQuery = """{"query": "%s", "params": %s }""".format(createRelationship, "${params}")


  val scn = scenario("Create Relationships")
    .during(30) {
    exec(chooseRandomNodes)
      .exec(
        http("create relationships")
          .post("/db/data/cypher")
          .header("X-Stream", "true")
          .body(cypherQuery)
          .asJSON
          .check(status.is(200)))
      .pause(0 milliseconds, 5 milliseconds)
  }

  setUp(
    scn.users(100).ramp(10).protocolConfig(httpConf)
  )
}

Когда вы запустите это, вы увидите поток параметров, которые мы отправили на наш почтовый запрос:

{"query": "START node1=node({id1}), node2=node({id2}) CREATE UNIQUE node1-[:KNOWS]->node2", "params": {"id1" : 98468, "id2" : 20147} }
{"query": "START node1=node({id1}), node2=node({id2}) CREATE UNIQUE node1-[:KNOWS]->node2", "params": {"id1" : 83557, "id2" : 26633} }
{"query": "START node1=node({id1}), node2=node({id2}) CREATE UNIQUE node1-[:KNOWS]->node2", "params": {"id1" : 22386, "id2" : 99139} }

Вы можете отключить это, но я просто хотел убедиться, что идентификаторы были случайными, и это помогает при отладке. Теперь мы можем запросить график. Для этого следующего моделирования я хочу увидеть ответы, возвращенные из Neo4j, и я хочу видеть узлы, связанные с 10 случайными узлами, переданными в виде массива JSON. Обратите внимание, что это немного отличается от предыдущего, и мы также проверяем, получили ли мы «данные» обратно в нашем запросе.

import com.excilys.ebi.gatling.core.Predef._
import com.excilys.ebi.gatling.http.Predef._
import akka.util.duration._
import bootstrap._
import util.parsing.json.JSONArray


class QueryGraph extends Simulation {
  val httpConf = httpConfig
    .baseURL("http://localhost:7474")
    .acceptHeader("application/json")
    .responseInfoExtractor(response => {
      println(response.getResponseBody)
      Nil
    })
    .disableResponseChunksDiscarding

  val rnd = new scala.util.Random
  val nodeRange = 1 to 100000
  val chooseRandomNodes = exec((session) => {
    session.setAttribute("node_ids", JSONArray.apply(List.fill(10)(nodeRange(rnd.nextInt(nodeRange length)))).toString())
  })

  val getNodes = """START nodes=node({ids}) MATCH nodes -[:KNOWS]-> other_nodes RETURN ID(other_nodes)"""
  val cypherQuery = """{"query": "%s", "params": {"ids": %s}}""".format(getNodes, "${node_ids}")

  val scn = scenario("Query Graph")
    .during(30) {
    exec(chooseRandomNodes)
      .exec(
        http("query graph")
          .post("/db/data/cypher")
          .header("X-Stream", "true")
          .body(cypherQuery)
          .asJSON
          .check(status.is(200))
          .check(jsonPath("data")))
      .pause(0 milliseconds, 5 milliseconds)
  }

  setUp(
    scn.users(100).ramp(10).protocolConfig(httpConf)
  )
}

Если мы посмотрим на вкладку сведений для этого моделирования, мы увидим небольшой всплеск в середине:

Снимок экрана Гатлинга

Это контрольный знак того, что сборка мусора в JVM происходит, и мы можем захотеть разобраться в этом. Отредактируйте файл neo4j / conf / neo4j-wrapper.conf и раскомментируйте ведение журнала сбора мусора, а также добавьте метки времени, чтобы улучшить видимость проблемы:

# Uncomment the following line to enable garbage collection logging
wrapper.java.additional.4=-Xloggc:data/log/neo4j-gc.log
wrapper.java.additional.5=-XX:+PrintGCDateStamps

Настройка производительности Neo4j заслуживает отдельного поста в блоге, но, по крайней мере, теперь у вас есть отличный способ проверить свою производительность, настроив JVM, кеш, оборудование, балансировку нагрузки и другие параметры. Не забывайте, что тестирование Neo4j напрямую — это круто, вы можете использовать Gatling для тестирования всего веб-приложения и измерения производительности от начала до конца.