Статьи

Перевод Cypher на Neo4j Java API 2.0

зашифровывать-перевод-2.0ish600x293

Около 6 месяцев назад мы рассмотрели, как перевести несколько строк Cypher в слишком много Java-кода в версии 1.9.x. С тех пор Cypher изменился, и я немного отстой в Java, поэтому я хотел поделиться несколькими различными способами перевести один в другой на тот случай, если вы застряли в середине восьмидесятых и изменили количество строк кода вы пишете в час.

Но сначала,

дай мне взять #Selfie
давайте сделаем некоторые данные. У Майкла Хангера есть серия постов в блоге о получении и создании данных в Neo4j., хорошо

украсть
позаимствовать его идеи. Давайте создадим 100 тыс. Узлов:

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}))

… И давайте создадим около 500 тыс. Отношений между ними:

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

… И давайте не забудем добавить индекс:

CREATE INDEX ON :User(username);

Теперь, когда мы смотрим на наши данные, мы видим:

Снимок экрана 2014-04-16 в 2.04.49 утра

Теперь, если мы хотим составить рекомендацию для 10 лучших пользователей, «Мишель 1» должна дружить, но не сейчас, мы бы написали что-то вроде этого:

MATCH (me:User {username:'Michelle1'}) -[:FRIENDS]- people -[:FRIENDS]- fof
WHERE NOT(me -[:FRIENDS]- fof)
RETURN fof, COUNT(people) AS friend_count
ORDER BY friend_count DESC
LIMIT 10

… И мы получили бы такую ​​ошибку после 60 секундного тайм-аута в окне браузера:

Снимок экрана 2014-04-16 в 2.29.52 утра

Cypher, начиная с 2.0.2, не оптимизирован для этого типа запросов (он будет готов), поэтому давайте обратимся к Java API. Первое, что мы хотим сделать, — это найти пользователя, а затем заставить его друзей просто привыкнуть к новым методам Java API.

@GET
    @Path("/friends/{username}")
    public Response getFriends(@PathParam("username") String username, @Context GraphDatabaseService db) throws IOException {
        List<String> results = new ArrayList<String>();
        try ( Transaction tx = db.beginTx() )
        {
            final Node user = IteratorUtil.singleOrNull(db.findNodesByLabelAndProperty(DynamicLabel.label("User"), "username", username));
 
            if(user != null){
                for ( Relationship relationship : user.getRelationships(FRIENDS, Direction.OUTGOING) ){
                    Node friend = relationship.getOtherNode(user);
                    results.add((String)friend.getProperty("username"));
                }
            }
        }
 
        return Response.ok().entity(objectMapper.writeValueAsString(results)).build();
    }

Вместо непосредственного перехода к индексу мы используем метод findNodesByLabelAndProperty, чтобы найти нашего пользователя. Также обратите внимание, что все заключено в блок Try с транзакцией. В 2.0 все взаимодействия с базой данных должны быть внутри транзакции. В связи с этим давайте рассмотрим список 10 лучших друзей друзей, которые не являются моими нынешними друзьями, по количеству общих друзей в Java:

@GET
    @Path("/fofs/{username}")
    public Response getFofs(@PathParam("username") String username, @Context GraphDatabaseService db) throws IOException {
        List<Map<String, Object>> results = new ArrayList<>();
 
        HashMap<Node, MutableInt> fofs = new HashMap<>();
        try ( Transaction tx = db.beginTx() )
        {
            final Node user = IteratorUtil.singleOrNull(db.findNodesByLabelAndProperty(DynamicLabel.label("User"), "username", username));
 
            findFofs(fofs, user);
            List<Map.Entry<Node, MutableInt>> fofList = orderFofs(db, fofs);
            returnFofs(results, fofList.subList(0, Math.min(fofList.size(), 10)));
        }
 
        return Response.ok().entity(objectMapper.writeValueAsString(results)).build();
    }

Я поместил findFofs, orderFofs и returnFofs в их собственные методы. Сначала мы рассмотрим findFofs, и я хочу, чтобы вы обратили внимание, потому что есть явная ошибка, которую я пропустил в первый раз, когда делал это, и повторяю здесь. Посмотри, сможешь ли ты заметить это.

private void findFofs(HashMap<Node, MutableInt> fofs, Node user) {
    List<Node> friends = new ArrayList<>();
 
    if (user != null){
        getFirstLevelFriends2(user, friends);
        getSecondLevelFriends2(fofs, user, friends);
    }
}
private void getFirstLevelFriends(Node user, List<Node> friends) {
     for ( Relationship relationship : user.getRelationships(FRIENDS, Direction.BOTH) ){
         Node friend = relationship.getOtherNode(user);
         friends.add(friend);
     }
 }

Теперь, здесь вы действительно хотите обратить внимание …

private void getSecondLevelFriends(HashMap<Node, MutableInt> fofs, Node user, List<Node> friends) {
    for ( Node friend : friends ){
        for (Relationship otherRelationship : friend.getRelationships(FRIENDS, Direction.BOTH) ){
            Node fof = otherRelationship.getOtherNode(friend);
            if ((!user.equals(fof) && !friends.contains(fof))) {
                MutableInt mutableInt = fofs.get(fof);
                if (mutableInt == null) {
                    fofs.put(fof, new MutableInt(1));
                } else {
                    mutableInt.increment();
                }
            }
        }
    }
}

Видел это? И я нет. Давайте проверим производительность этой конечной точки с помощью ApacheBench :

ab -k -c 1 -n 1 'http://127.0.0.1:7474/example/service/fofs/Michelle1'

Наши результаты намного лучше, чем раньше. 2,670 секунды против времени ожидания, которое мы видели раньше.

Concurrency Level:      1
Time taken for tests:   2.670 seconds
Complete requests:      1
Failed requests:        0
Write errors:           0
Keep-Alive requests:    1
Total transferred:      655 bytes
HTML transferred:       522 bytes
Requests per second:    0.37 [#/sec] (mean)
Time per request:       2670.414 [ms] (mean)
Time per request:       2670.414 [ms] (mean, across all concurrent requests)
Transfer rate:          0.24 [Kbytes/sec] received

Это огромное улучшение, но Neo4j выполняет миллионы обходов в секунду и может предоставить рекомендации в режиме реального времени … 2,670 секунды просто не звучат правильно. Итак, давайте немного покопаемся с помощью YourKit .

YourKit

YourKit — это профилировщик Java, который мы можем подключить к работающему серверу Neo4j, и он позволит нам увидеть, что происходит, когда мы добавим немного больше нагрузки, чем 1 запрос. Это не очевидно, но когда вы запускаете Neo4j, имя, которое он отображает, называется «Bootstrapper». Посмотрите руководство YourKit для более подробной информации .

Прикрепите Yourkit

ab -k -c 8 -n 800 'http://127.0.0.1:7474/example/service/fofs/Michelle1'

A little while after we start collecting profile information and begin running our test, this pops up:

Снимок экрана 2014-04-22 в 10.58.27 утра

Oh oh… something is obviously wrong… let’s dig in.

Снимок экрана 2014-04-22 в 10.59.07 утра

So something in getSecondLevelFriends is wasting time doing what looks like nothing…

private void getSecondLevelFriends2(HashMap<Node, MutableInt> fofs, Node user, List<Node> friends) {
        for ( Node friend : friends ){
            for (Relationship otherRelationship : friend.getRelationships(FRIENDS, Direction.BOTH) ){
                Node fof = otherRelationship.getOtherNode(friend);
                if ((!user.equals(fof) && !friends.contains(fof))) {

… and there it is. We’re calling contains on a List of Nodes instead of a Set of Nodes, so it’s going to scan it instead of go right to it. Log(n) vs Log(1) type of problem because I used the wrong data structure. So let’s change this to a Set and try it again.

ab -k -c 1 -n 1 'http://127.0.0.1:7474/example/service/fofs/Michelle1'

Our results are WAY better than before. 91 milliseconds vs the 2.670 seconds we were taking before, vs the timeout from where we started.

Concurrency Level:      1
Time taken for tests:   0.091 seconds
Complete requests:      1
Failed requests:        0
Write errors:           0
Keep-Alive requests:    1
Total transferred:      655 bytes
HTML transferred:       522 bytes
Requests per second:    10.99 [#/sec] (mean)
Time per request:       91.019 [ms] (mean)
Time per request:       91.019 [ms] (mean, across all concurrent requests)
Transfer rate:          7.03 [Kbytes/sec] received

Let’s try giving it some load:

ab -k -c 8 -n 800 'http://127.0.0.1:7474/example/service/fofs/Michelle1'

… and now we’re getting 55 requests per second real time recommendations on my laptop.

Concurrency Level:      8
Time taken for tests:   14.536 seconds
Complete requests:      800
Failed requests:        0
Write errors:           0
Keep-Alive requests:    800
Total transferred:      524000 bytes
HTML transferred:       417600 bytes
Requests per second:    55.04 [#/sec] (mean)
Time per request:       145.361 [ms] (mean)
Time per request:       18.170 [ms] (mean, across all concurrent requests)
Transfer rate:          35.20 [Kbytes/sec] received

As always, the full source code is available on Github.

One last thing… in Neo4j 2.1… it goes almost twice as fast.

Concurrency Level:      8
Time taken for tests:   8.523 seconds
Complete requests:      800
Failed requests:        0
Write errors:           0
Keep-Alive requests:    800
Total transferred:      524000 bytes
HTML transferred:       417600 bytes
Requests per second:    93.86 [#/sec] (mean)
Time per request:       85.234 [ms] (mean)
Time per request:       10.654 [ms] (mean, across all concurrent requests)
Transfer rate:          60.04 [Kbytes/sec] received

Now that’s Amazing.

[Editor’s Note: Download your free copy of Neo4j now]