Как я могу получить и разбить на страницы ленту пользователей в IBM Graph (TitanDB) с помощью Gremlin / Tinkerpop

#titan #gremlin #tinkerpop #tinkerpop3 #ibm-graph

#titan #gremlin #tinkerpop #tinkerpop3 #ibm-graph

Вопрос:

У меня есть очень простая лента новостей, смоделированная в IBM Graph (TitanDB, поддерживаемая Cassandra), как показано ниже:

введите описание изображения здесь

Я пытаюсь написать запрос, который выполняет следующее:

  1. Начните с вершины USER: John.Smith
  2. Получить 15 самых последних сообщений от пользователей FRIENDS в сочетании с его собственными.
  3. Проверьте, USER: John.Smith понравилась ли какая-либо из этих записей, и верните в виде простого is_liked логического свойства для каждой записи.

Для этого запроса есть несколько предварительных условий:

  • В каждой возвращаемой записи также должны быть возвращены свойства публикации USER . Для решения этого вопроса требуется только avatar свойство.
  • Мне нужно иметь возможность разбивать эти результаты на страницы. т. Е. Как только я получу 15 лучших сообщений, мне нужно будет иметь возможность вернуть следующие 15, затем следующий и т.д.

У меня нет проблем с поиском друзей пользователей и их LATEST_POSTS :

 g.V().hasLabel("USER").has("userid", "John.Smith").both("FRIEND").out("LATEST_POST");
  

Я прочитал документацию по Tinkerpop, но все еще теряюсь в том, как начать строить этот запрос, чтобы соответствовать моим требованиям.

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

Заранее спасибо.

Ответ №1:

Для данной схемы graph запрос будет выглядеть примерно так:

 g.V().has("user", "userid", "John.Smith").as("john").
  union(identity(), both("FRIEND")).as("user").
  out("LATEST_POST").
  flatMap(emit().repeat(out("PREVIOUS_POST")).range(page * pageSize, (page   1) * pageSize)).as("post").
  choose(__.in("LIKED").where(eq("john")), constant(true), constant(false)).as("likedByJohn")
  select("user", "post", "likedByJohn")
  

Но Алаа уже указывал, что этот подход не будет масштабироваться и как вы могли бы улучшить свою графическую схему.

Комментарии:

1. Спасибо. Это здорово, поскольку оно правильно отвечает на мой представленный вопрос. Но меня все еще смущает предложение Алаа. Должен ли я подключать каждую запись напрямую к ее автору через edge, в отличие от схемы связанных списков, которую я использую в настоящее время? И если да, означает ли это, что запрос должен будет просмотреть каждое сообщение каждого друга только для того, чтобы получить 15 лучших элементов ленты новостей? Как я упоминал, IBM graph, похоже, не имеет индексов, ориентированных на вершины, поэтому, конечно, это тоже не будет масштабироваться? Или я ошибаюсь?

2. Да, извините, я не знал об отсутствии индексов, ориентированных на вершины. В этом случае другая схема, вероятно, не добавит большой ценности. Я действительно понятия не имею, почему они не поддерживают индексы, ориентированные на вершины; возможно, один из их разработчиков может прокомментировать, как, по их мнению, следует решать такого рода проблемы.

3. К сожалению, у нас пока не было времени поддерживать индексы, ориентированные на вершины, из-за приоритетов. Исходя из нашего опыта работы с нашими пользователями, просмотр больших графиков не был проблемой, особенно когда речь идет о нескольких 100 и даже нескольких 1000 вершинах на ребро . У нас есть пользователи, которые загружают несколько миллиардов узлов и имеют приличную пропускную способность. Я бы посоветовал вам использовать смешанные индексы для ускорения обхода. Вы могли бы помечать последние публикации логическим свойством (latest) и индексировать его. Теперь вы можете легко писать запросы для извлечения вершин, которые has('latest',true) возвращают последние сообщения

Ответ №2:

Вы должны проверить рецепт разбивки на страницы в http://tinkerpop.apache.org/docs/3.2.3-SNAPSHOT/recipes/#pagination. Вот упрощенный способ получения одного диапазона / страницы за раз

 gremlin> g.V().hasLabel('person').range(0,2)
==>v[1]
==>v[2]
gremlin> g.V().hasLabel('person').range(2,4)
==>v[4]
==>v[6]
  

Что касается используемой вами модели, я бы не стал использовать край LATEST_POST, поскольку вам нужно будет постоянно обновлять этот край каждый раз, когда у пользователя появляется новая запись. Лучше добавить свойство timestamp к записи, и вы всегда сможете отсортировать возвращенные результаты по метке времени, чтобы получить последнюю запись.

Комментарии:

1. Спасибо, Алаа. Я знаю о шаге range (), но я не уверен, как бы я применил его в соответствии с другими моими требованиями. Я смоделировал ленты как связанный список с узлом LATEST_POST, в первую очередь из-за того, что IBM Graph, похоже, пока не имеет индексов, ориентированных на вершину. Я был обеспокоен тем, что выполнение того, что вы предлагаете, приведет к тому, что запросу придется сканировать каждое сообщение каждого друга, чтобы собрать и упорядочить их. И не приведет ли это к тому, что с течением времени к каждому пользователю будет прикреплено огромное количество записей, что приведет к постепенному замедлению всех запросов? Возможно, я чего-то недопонимаю.

2. Или вы имели в виду сохранить модель связанного списка для временной шкалы пользователей и просто прикрепить их всех друг к другу через РАЗМЕЩЕННЫЙ край вместе с отметкой времени в вершинах ПУБЛИКАЦИИ? Я думал, что я мог бы просто удалить текущий край LATEST_POST и прикрепить новую запись. Затем прикрепите оставшуюся часть потенциально длинного списка к новой записи. Прикрепление каждой записи непосредственно к пользователю действительно упростило бы мой запрос, но, как уже упоминалось, я обеспокоен тем, что производительность запроса со временем ухудшается по мере добавления пользователями новых записей.

3. Что делает этап диапазона, так это фильтрует вершины из индекса x в индекс y. Итак, если у вас есть запрос, который возвращает все вершины POST , добавление к нему диапазона вернет только подмножество вершин. g.V().hasLabel("USER").has("userid", "John.Smith").both("FRIEND").out("LATEST_POST").range(0,5) вернет первые 5 сообщений, вы не можете заставить ваш запрос gremlin возвращать набор данных, затем возвращать другой набор и т.д. … в одном вызове gremlin. Что вы могли бы сделать, так это отправить диапазон в качестве параметра в вашем запросе, чтобы каждый вызов возвращал подмножество ваших данных.

4. Хорошо, думаю, я понял. Означает ли это, что нет способа вернуть подмножество сообщений друзей пользователей, не просматривая при этом каждое отдельное сообщение каждого отдельного друга? Итак, если Джон. У Смита 100 друзей, у каждого по 1000 сообщений, и чтобы получить самые старые сообщения в ленте пользователей, мне пришлось бы обойти 100 000 узлов?

5. Правильно. Но вам следует создать индекс для свойства timestamp при создании вашей схемы. Я думаю, что создание индекса в свойстве time приведет к внутреннему преобразованию в поиск по индексу вместо обхода всех узлов.