Я пытаюсь смоделировать двунаправленную дружбу в MongoDB.«Двунаправленный» означает, что, как и в Facebook, но в отличие от Twitter, если вы дружите с Сэмом, то Сэм тоже должен дружить с вами.В MongoDB обычно рекомендуемое решение ( пример ) выглядит примерно так:
- Создайте коллекцию
User
(или узлы, чтобы использовать правильный термин теории графов)) и коллекцию Friendship
(или ребра) - Каждый документ
User
содержит встроенный массив friends
, каждый элемент которого содержит ObjectID каждого друга.Каждый элемент может также кэшировать информацию о друге, доступную только для чтения (например, имя, URL-адрес фотографии), что позволяет избегать перекрестных запросов для общих случаев использования, таких как «отображение списка моих друзей». - Добавление дружеских связей включает вставкуновый
Friendship
документ, а затем $push
-внедрение идентификатора объекта Friendship
в массив friends
у обоих пользователей, а также кэшированная информация только для чтения о друзьях (например, имя), чтобы избежать многодокументных запросовпри отображении списков друзей.
Я рассматриваю другой дизайн, в котором вместо отдельной коллекции Friendship
данные края будут храниться (дублироваться) в обоих узлах двунаправленной связи.Например:
{
_id: new ObjectID("111111111111111111111111"),
name: "Joe",
pictureUrl: "https://foo.com/joe.jpg",
invites: [
... // similar schema to friends array below
],
friends: [
{
friendshipId: new ObjectID("123456789012345678901234"),
lastMeeting: new Date("2019-02-07T20:35:55.256+00:00"),
user1: {
userId: new ObjectID("111111111111111111111111"),
name: "Joe", // cached, read-only data to avoid multi-doc reads
pictureUrl: "https://foo.com/joe.jpg",
},
user2: {
userId: new ObjectID("222222222222222222222222"),
name: "Bill", // cached, read-only data to avoid multi-doc reads
pictureUrl: "https://foo.com/bill.jpg",
},
}
]
},
{
_id: new ObjectID("222222222222222222222222"),
name: "Bill",
pictureUrl: "https://foo.com/bill.jpg",
invites: [
... // similar schema to friends array below
],
friends: [
{
friendshipId: new ObjectID("123456789012345678901234"),
lastMeeting: new Date("2019-02-07T20:35:55.256+00:00"), // shared data about the edge
user1: { // data specific to each friend
userId: new ObjectID("111111111111111111111111"),
name: "Joe", // cached, read-only data to avoid multi-doc reads
pictureUrl: "https://foo.com/joe.jpg",
},
user2: { // data specific to each friend
userId: new ObjectID("222222222222222222222222"),
name: "Bill", // cached, read-only data to avoid multi-doc reads
pictureUrl: "https://foo.com/bill.jpg",
},
}
]
}
Вот как я планирую справиться со следующим:
- Чтения - все операции чтения для общих операций происходят только из отдельных
User
документов.Информация высокого уровня о друзьях (например, имя, URL-адрес изображения) кэшируется в массиве friends
. - Приглашение друга - добавьте новый документ в массив
invites
, встроенный в обоих пользователей (не показан выше), который похож по структуре и функциональности на коллекцию friends
, показанную выше - Приглашениепринято - с помощью
updateMany
добавьте новый идентичный внедренный документ в массив friends
обоих пользователей и $pull
элемент из массива invites
обоих пользователей.Первоначально я буду использовать транзакции с несколькими документами для этих обновлений, но поскольку добавление дружеских отношений не является критичным по времени, это можно адаптировать для использования возможной согласованности. - Дружба аннулирована - используйте
updateMany
с фильтром для {'friends.friendshipId': new ObjectID("123456789012345678901234")}
до $pull
поддокумента дружбы из friends
массивов обоих пользователей.Как и выше, вначале могут использоваться многодокументные транзакции, а затем возможная согласованность позднее, если это необходимо для масштабирования. - Обновление кэшированных данных - если пользователь изменяет информацию, кэшированную в
friends
(например, имя или URL-адрес изображения), это необычная операция, которая может выполняться медленно и по одному документу за раз, поэтомувозможна согласованность.
У меня есть две основные проблемы, о которых я хотел бы услышать ваш совет:
Каковы проблемы и недостатки описанного подходавыше?Я знаю об очевидных вещах: дополнительное хранилище, более медленные обновления, необходимость добавления логики очередей и повторных попыток для поддержки возможной согласованности, а также риск несинхронизации пограничных данных между двумя их копиями.Я думаю, что я в порядке с этими проблемами.Но есть ли другие, неочевидные проблемы, с которыми я, вероятно, столкнусь?
Вместо того, чтобы иметь поля user1
и user2
для каждого узла ребра, было бы этолучше использовать 2-элементный массив вместо?Почему или почему нет?Вот пример того, что я имею в виду:
friends: [
{
friendshipId: new ObjectID("123456789012345678901234"),
lastMeeting: new Date("2019-02-07T20:35:55.256+00:00"),
users: [
{
userId: new ObjectID("111111111111111111111111"),
name: "Joe", // cached, read-only data to avoid multi-doc reads
pictureUrl: "https://foo.com/joe.jpg",
},
{
userId: new ObjectID("222222222222222222222222"),
name: "Bill", // cached, read-only data to avoid multi-doc reads
pictureUrl: "https://foo.com/bill.jpg",
},
],
}
]
Кстати, я знаю, что графические базы данных и даже реляционные базы данных лучше в моделировании отношений по сравнению с MongoDB.Но по ряду причин я остановился на MongoDB на данный момент, поэтому, пожалуйста, ограничьте ответы на решения MongoDB, вместо того, чтобы указывать мне на использование графической или реляционной базы данных.Спасибо!