Какой самый быстрый запрос друзей друзей ArangoDB (с подсчетом)

Я пытаюсь использовать ArangoDB для получения списка друзей друзей. Не только базовый список друзей друзей, я также хочу знать, сколько общих друзей у пользователя и друга друга, и отсортировать результат. После нескольких попыток (пере)писать наиболее эффективный запрос AQL, вот что у меня получилось:

LET friends = (
  FOR f IN GRAPH_NEIGHBORS('graph', @user, {"direction": "any", "includeData": true, "edgeExamples": { name: "FRIENDS_WITH"}})
  RETURN f._id
)

LET foafs = (FOR friend IN friends
  FOR foaf in GRAPH_NEIGHBORS('graph', friend, {"direction": "any", "includeData": true, "edgeExamples": { name: "FRIENDS_WITH"}})
    FILTER foaf._id != @user AND foaf._id NOT IN friends
    COLLECT foaf_result = foaf WITH COUNT INTO common_friend_count
    RETURN {
      user: foaf_result,
      common_friend_count: common_friend_count
    }
)
FOR foaf IN foafs
  SORT foaf.common_friend_count DESC
  RETURN foaf

К сожалению, производительность не так хороша, как хотелось бы. По сравнению с версиями того же запроса (и данных) в Neo4j, AQL кажется немного медленнее (в 5-10 раз).

Я хотел бы знать... Как я могу улучшить наш запрос, чтобы он работал лучше?


person Terry Seidler    schedule 22.10.2015    source источник


Ответы (1)


Я один из основных разработчиков ArangoDB и попытался оптимизировать ваш запрос. Поскольку у меня нет вашего dataset, я могу говорить только о своем тесте dataset и был бы рад узнать, сможете ли вы подтвердить мои результаты.

Во-первых, если все, что я работаю на ArangoDB 2.7, но в этом конкретном случае я не ожидаю большой разницы в производительности по сравнению с 2.6.

В моем dataset я мог бы выполнить ваш запрос за ~ 7 секунд. Первое исправление: в заявлении о друзьях вы используете includeData: true и возвращаете только _id. С includeData: false GRAPH_NEIGHBORS напрямую возвращает _id и здесь мы также можем избавиться от подзапроса

LET friends = GRAPH_NEIGHBORS('graph', 
                              @user,
                              {"direction": "any",
                               "edgeExamples": { 
                                   name: "FRIENDS_WITH"
               }})

Это уменьшило время до ~ 1,1 секунды на моей машине. Поэтому я ожидаю, что это будет близко к производительности Neo4J.

Почему это оказывает большое влияние? Внутри мы сначала находим значение _id, фактически не загружая документы JSON. В вашем запросе вам не нужны никакие из этих данных, поэтому мы можем смело продолжать не открывать его.

А теперь самое главное

Ваш запрос идет «логическим» путем и сначала получает соседей пользователей, затем находит их соседей, подсчитывает, как часто встречается foaf, и сортирует его. Это должно создать полную сеть foaf в памяти и отсортировать ее как единое целое.

Вы также можете сделать это по-другому: 1. Найти все friends пользователя (только _ids) 2. Найти все foaf (полный документ) 3. Для каждого foaf найти все foaf_friends (только _ids) 4. Найти пересечение friends и foaf_friends и ПОСЧИТАЙТЕ их

Этот запрос будет выглядеть следующим образом:

LET fids = GRAPH_NEIGHBORS("graph",
                           @user,
                           {
                             "direction":"any",
                             "edgeExamples": {
                               "name": "FRIENDS_WITH"
                              }
                           }
                          )
FOR foaf IN GRAPH_NEIGHBORS("graph",
                            @user,
                            {
                              "minDepth": 2,
                              "maxDepth": 2,
                              "direction": "any",
                              "includeData": true,
                              "edgeExamples": {
                                "name": "FRIENDS_WITH"
                              }
                            }
                           )
  LET commonIds = GRAPH_NEIGHBORS("graph",
                                  foaf._id, {
                                    "direction": "any",
                                    "edgeExamples": {
                                      "name": "FRIENDS_WITH"
                                     }
                                  }
                                 )
  LET common_friend_count = LENGTH(INTERSECTION(fids, commonIds))
  SORT common_friend_count DESC
  RETURN {user: foaf, common_friend_count: common_friend_count}

Который на моем тестовом графике выполнялся за ~0,024 сек.

Так что это дало мне время выполнения в 250 раз быстрее, и я ожидаю, что это будет быстрее, чем ваш текущий запрос в Neo4j, но, поскольку у меня нет вашего dataset, я не могу его проверить, было бы хорошо если бы вы могли это сделать и сказать мне.

И последнее

С edgeExamples: {name : "FRIENDS_WITH" } то же самое, что и с includeData, в этом случае мы должны найти реальную грань и заглянуть в нее. Этого можно было бы избежать, если бы вы хранили ребра в отдельных коллекциях в зависимости от их имени. А затем также удалите edgeExamples. Это еще больше повысит производительность (особенно если много ребер).

Будущее

Следите за нашим следующим выпуском, прямо сейчас мы добавляем некоторые дополнительные функции в AQL, которые значительно упростят запросы к вашему делу и должны дать еще один прирост производительности.

person mchacki    schedule 23.10.2015
comment
Спасибо! Я проверю, проверю и приму ваш ответ в понедельник! Нам очень приятно, что вы нашли время ответить на наш вопрос ;) - person Terry Seidler; 23.10.2015
comment
В нашем случае ваше первое улучшение было значительно быстрее нашей версии. Особенно наши самые медленные запросы выиграли от ваших улучшений. Это действительно приблизило результат AQL к версии Neo4j. Что касается 2-го запроса - он сделал наши foaf-запросы в худшем случае быстрее, но запросы в лучшем случае немного медленнее :(. В любом случае, первое улучшение нам очень помогло ;). - person Terry Seidler; 26.10.2015