Модель данных - moevm/nosql2h21-vk-comments GitHub Wiki

Описание нереляционной модели данных

Графическое представление

NoSQL модель

Подробное описание

БД содержит 3 коллекции "Comments", "Groups" и "Users" Users содержит массив объектов типа User

  • _id - уникальный идентификатор пользователя
  • first_name - имя пользователя
  • last_name - фамилия пользователя
  • town - город
  • birtdate - день рождения
  • friend - список id друзей

Groups содержит массив объектов типа Group

  • _id - уникальный идентификатор группы
  • title - название группы
  • user_count - число пользователей в группе

Comments содержит массив объектов типа Comment

  • _id - уникальный идентификатор комментария
  • user - информация о пользователе, написавшем комментарий, объект типа User_short
  • _id - уникальный идентификатор пользователя
  • first_name - имя пользователя
  • last_name - фамилия пользователя
  • group - информация о группе, в которой оставлен комментарий, объект типа Group_short
  • _id - уникальный идентификатор группы
  • title - название группы
  • text - текст комментария
  • time - время, в которое был создан комментарий
  • likes - id пользователей, поставивших лайк

Оценка объема

Коллекция Users: Найдем объем для одного пользователя (тип User):

  • _id - ObjectId, 8 байт
  • first_name - String, пусть максимальная длина имени - 15 символов, 15 байт
  • last_name - String, пусть максимальная длина фамилии - 15 символов, 15 байт
  • town - String, пусть макисмальная длина города - 25 символов, 25 байт
  • birtdate - Date, 8 байт
  • friend - Array[ObjectId], пусть среднее число друзей - Nf, 8*Nf

Итого: Vu = 8 + 15 + 15 + 25 + 8 + 8Nf = 71+8Nf байт


Коллекция Groups: Найдем объем для одной группы (тип Group):

  • _id - ObjectId, 8 байт
  • title - String, пусть максимальная длина названия - 40 символов, 40 байт
  • user_count - Int, 8 байт

Итого: Vg = 8 + 40 + 8 = 56 байт


Коллекция Comments: Найдем объем для одного комментария (тип Comments):

  • _id - ObjectId, 8 байт
  • user - User_short, 38 байт
  • _id - ObjectId, 8 байт
  • first_name - String, 15 байт
  • last_name - String, 15 байт
  • group - Group_short, 48 байт
  • _id - ObjectId, 8 байт
  • title - String, 40 байт
  • text - String, предположим, что средняя длина комментария - 255 символов, 255 байт
  • time - Date, 8 байт
  • likes - Array[ObjectId], пусть среднее число лайков - Nl, 8*Nl байт

Итого: Vc = 8 + 38 + 48 + 255 + 8 + 8Nl = 357 + 8Nl байт


Объем данных, необходимый для хранения Nu пользователей, Ng групп и Nc комментариев:

V(Nu, Ng, Nc) = Nu * Vu + Ng * Vg + Nc * Vc

V(Nu, Ng, Nc) = Nu * (71 + 8 * Nf) + Ng * 56 + Nc * (357 + 8 * Nl)

Для оценки объема предположим, что только каждый сотый пользователь создает группу (Ng = Nu/100) и каждый пользователь оставил в среднем 100 комментариев (Nc = 100*Nu). Так же предположим, что среднее число лайков для комментария - 5 и среднее число друзей - 20.

Тогда для 1000 пользователей: V = 1000 * (71 + 8 * 20) + 10 * 56 + 100000 * (357 + 8 * 5) = 39931560 байт = 4.76 мбайт

Вычисление чистого объема данных

Коллекции User и Group остаются без изменений: Vu = 71+8*Nf байт Vg = 56 байт

В коллекции Comments не цчитываются поля user и group: Vc = 8 + 255 + 8 + 8Nl = 271 + 8Nl байт Для 1000 пользователей при тех же допущениях получим: V_clean = 1000 * (71 + 8 * 20) + 10 * 56 + 100000 * (271 + 8 * 5) = 31331560 байт = 3.7 мбайт Отношение v_clean/v = 0.784

Запросы

В треугольных скобках указаны параметры запроса <example>

  • Поиск группы по названию
db.groups.find({title: /<title>/})

Сложность - O(n) Используется коллекций - 1

  • Поиск комментария по ключевой фразе
db.comments.aggregate([
    {$match: {text: /<key>/}},
    {$addFields: { likes_count: { $size: "$likes" } }},
    {$project: {likes: 0}}
])

Используется коллекций - 1

  • Получение пользователей, лайкнувших комментарий
db.comments.aggregate([
    {$match: {_id: 0}},
    {$project: {likes: 1}},
    {$lookup: {
        from: "users",
        localField: "likes",
        foreignField: "_id",
        as: "likes"
    }}
])

Используется коллекций - 2

  • Получение наиболее активных комментаторов в группе
db.comments.aggregate([
    {$match: {"group._id": <group_id>}},
    {$group: {
        _id: "$user._id",
        first_name: { $first: "$user.first_name"},
        last_name: { $first: "$user.last_name"},
        count: { $sum: 1 }
    }},
    {$sort: {count: -1}},
    {$limit: <rows_count>}
])

Используется коллекций - 1

  • Получение комментариев пользователя
db.comments.aggregate([
    {$match: {"user._id": <user_id>}},
    {$addFields: { likes_count: { $size: "$likes" } }},
    {$project: {likes: 0, user: 0}}
])

Используется коллекций - 1

  • Получение друзей пользователя
db.users.aggregate([
    {$match: {"_id": <user_id>}},
    {$lookup: {
        from: "users",
        localField: "friends",
        foreignField: "_id",
        as: "friends"
    }},
    {$project: {friends: 1}}
])

Используется коллекций - 1, но в крайнем случае может быть считана 2 раза

  • Получение связей по комментариям в одной группе
db.comments.distinct("user", {"group._id": {
    $in: db.comments.distinct("group._id", {"user._id": <user_id>})
},
    "user._id": {$ne: <user_id>}
})

Используется коллекций - 1, но в крайнем случае может быть считана 2 раза Число запросов - 2

  • Количество лайков пользователя
db.comments.count({
    likes: <user_id>
})

Используется коллекций - 1

  • Количество комментариев пользователя
db.comments.count({
    "user._id": <user_id>
})

Используется коллекций - 1

  • Данные для диаграммы количества комментариев по времени
db.comments.aggregate([
    {$match: {
        text: /<key>/
    }},
    {$addFields: {
       year: {$year: "$time"},
       month: {$month: "$time"},
       day: {$dayOfMonth: "$time"} ,
       hour: {$hour: "$time"},
       minute: {$minute: "$time"}
    }},
    {$match: {
        $and: [
            {$or: [
                {year: {$gt: <dateFrom.year>}},
                {$and: [{year: {$eq: <dateFrom.year>}}, {month: {$gt: <dateFrom.month>}}]},
                {$and: [{year: {$eq: <dateFrom.year>}}, {month: {$eq: <dateFrom.month>}}, {day: {$gt: <dateFrom.day>}}]}
            ]},
            {$or: [
                {year: {$lt: <dateTo.year>}},
                {$and: [{year: {$eq: <dateTo.year>}}, {month: {$lt: <dateTo.month>}}]},
                {$and: [{year: {$eq: <dateTo.year>}}, {month: {$eq: <dateTo.month>}}, {day: {$lt: <dateTo.day>}}]}
            ]},
        ]
    }},
    {$match: {
        $and: [
            {$or: [
                {hour: {$gt: <timeFrom.hour>}},
                {$and: [{hour: {$eq: <timeFrom.hour>}}, {minute: {$gt: <timeFrom.minute>}}]}
            ]},
            {$and: [
                {hour: {$gt: <timeTo.hour>}},
                {$and: [{hour: {$eq: <timeTo.hour>}}, {minute: {$gt: <timeTo.minute>}}]}
            ]}
        ]
    }},
    {$group: {
        _id: {$concat: [{$toString: "$year"}, "-", {$toString: "$month"}, "-", {$toString: "$day"}]},
        count: {$sum: 1}
    }}
])

Используется коллекций - 1

  • DEBUG: список пользователей
db.users.aggregate([
     {$addFields: {
        age: {$subtract: [{$year: ISODate()}, {$year: "$birthdate"}]}
     }},
     {$match: {
        first_name: /<first_name>/,
        last_name: /<last_name>/,
        town: /<town>/,
        age: {$gt: <ageFrom>, $lt: <ageTo>}
     }},
     {$project: {
        first_name: 1,
        last_name: 1,
        birthdate: 1,
        town: 1
     }}
])

Используется коллекций - 1

  • DEBUG: список групп
db.groups.aggregate([
    {$lookup: {
        from: "comments",
        localField: "_id",
        foreignField: "group._id",
        as: "comments"
    }},
    {$addFields: {
        comments_count: {$size: "$comments"}
    }},
    {$match: {
        title: /<title>/,
        users_count: {$gte: <ucFrom>, $lte: <ucTo>},
        comments_count: {$gte: <ccFrom>, $lte: <ccTo>}
    }},
    {$project: {
        comments: 0
    }}
])

Используется коллекций - 2

  • DEBUG: список комментариев
db.getSiblingDB("vk-comments").comments.aggregate([
    {$addFields: {
       year: {$year: "$time"},
       month: {$month: "$time"},
       day: {$dayOfMonth: "$time"} ,
       hour: {$hour: "$time"},
       minute: {$minute: "$time"},
       likes_count: {$size: "$likes"}
    }},
    {$match: {
        $and: [
            {$or: [
                {year: {$gt: <dateFrom.year>}},
                {$and: [{year: {$eq: <dateFrom.year>}}, {month: {$gt: <dateFrom.month>}}]},
                {$and: [{year: {$eq: <dateFrom.year>}}, {month: {$eq: <dateFrom.month>}}, {day: {$gt: <dateFrom.day>}}]}
            ]},
            {$or: [
                {year: {$lt: <dateTo.year>}},
                {$and: [{year: {$eq: <dateTo.year>}}, {month: {$lt: <dateTo.month>}}]},
                {$and: [{year: {$eq: <dateTo.year>}}, {month: {$eq: <dateTo.month>}}, {day: {$lt: <dateTo.day>}}]}
            ]},
        ]
    }},
    {$match: {
        $and: [
            {$or: [
                {hour: {$gt: <timeFrom.hour>}},
                {$and: [{hour: {$eq: <timeFrom.hour>}}, {minute: {$gt: <timeFrom.minute>}}]}
            ]},
            {$and: [
                {hour: {$gt: <timeTo.hour>}},
                {$and: [{hour: {$eq: <timeTo.hour>}}, {minute: {$gt: <timeTo.minute>}}]}
            ]}
        ]
    }},
    {$match: {
        text: /<text>/,
        likes_count: {$gte: <likesFrom>, $lte: <likesTo>},
        "user.first_name": /<first_name>/,
        "user.last_name": /<last_name>/,
    }},
    {$project: {
        user: 1,
        text: 1,
        time: 1,
        likes_count: 1
    }}
])

Используется коллекций - 1

Описание реляционной модели данных

Графическое представление

SQL модель

Подробное описание

User - содержит информацию о пользователе

  • id - уникальный идентификатор пользователя
  • first_name - имя пользователя
  • last_name - фамилия пользователя
  • town - город
  • birtdate - день рождения

Group - содержит информацию о группе

  • id - уникальный идентификатор группы
  • title - название группы
  • user_count - число пользователей в группе

Comment - содержит информацию о комментарии

  • _id - уникальный идентификатор комментария
  • user_id - id пользователя, оставившего комментарий
  • group_id - id группы, в которой был оставлен комментарий
  • text - текст комментария
  • time - время, в которое был создан комментарий

Like - содержит информацию о лайках

  • user_id - id пользователя, поставившего лайк
  • comment_id - id комментарий, под которым был поставлен лайк

Friends - содержит информация о друзьях. Пользоавтель A дружит с пользователем B, если существует строка, содержащая их id.

  • id1 - id пользователя, дружащего с пользователем с id == id2
  • id2 - id другого пользователя, дружащего с пользователем с id == id1

Оценка объема

Сущность User

  • id - Int, 8 байт
  • first_name - String, 15 байт
  • last_name - String, 15 байт
  • town - String, 25 байт
  • birtdate - Date, 8 байт

Итого: Vu = 8 + 15 + 15 + 25 + 8 = 71 байт


Сущность Group

  • id - Int, 8 байт
  • title - String, 40 байт
  • user_count - Int, 8 байт

Итого: Vg = 8 + 40 + 8 = 56 байт


Сущность Comment

  • _id - Int, 8 байт
  • user_id - Int, 8 байт
  • group_id - Int, 8 байт
  • text - String, 255 байт
  • time - Date, 8 байт

Итого: Vc = 8 + 8 + 8 + 255 + 8 = 287 байт


Сущность Likes

  • user_id - Int, 8 байт
  • comment_id - Int, 8 байт

Итого: Vl = 8 + 8 = 16 байт


Сущность Friends

  • id1 - Int, 8 байт
  • id2 - Int, 8 байт

Итого: Vf = 8 + 8 = 16 байт


Объем данных, необходимый для хранения Nu пользователей, Ng групп и Nc комментариев:

V(Nu, Ng, Nc) = Nu * Vu + Ng * Vg + Nc * Vc + Nu * Nf * Vf + Nc * Nl * Vl

V(Nu, Ng, Nc) = Nu * 71 + Ng * 56 + Nc * 287 + Nu * Nf * 16 + Nc * Nl * 16 Вынесем Nu и Nc за скобки: V(Nu, Ng, Nc) = Nu * (71 + Nf * 16) + Ng * 56 + Nc * (287 + Nl * 16)

Оценим объем для 1000 пользователей, испульзуя те же предположения, что и для нереляционной схемы.

V = 1000 * (71 + 20 * 16 ) + 10 * 56 + 100000 * (287 + 5 * 16) = 37091560 байт = 4.42 мбайт

Вычисление чистого объема

Для вычисления чистого объема не будем учитывать поля user_id и group_id в сущности Comment

Vc = 8 + 255 + 8 = 271 байт

Объем для 1000 польователей при тех же предположениях: V_clean = 1000 * (71 + 20 * 16 ) + 10 * 56 + 100000 * (271 + 5 * 16) = 35491560 байт = 4.23 мбайт

Найдем отношение V_clean/V = 35491560/37091560 = 0.95

Запросы

  • Поиск группы по названию
SELECT * FROM "Groups" WHERE "title" LIKE '%<title>%'

Используется коллекций - 1

  • Поиск комментария по ключевой фразе
SELECT C.*, count(L.*) as likes_count  FROM "Comments" C
    INNER JOIN "Likes" L on C.id = L.comment_id
WHERE C.text LIKE '%<key>%'
GROUP BY  C.id

Используется таблиц - 2

  • Получение пользователей, лайкнувших комментарий
SELECT DISTINCT U.* FROM "Users" U
    INNER JOIN "Likes" L on U.id = L.user_id
WHERE L.comment_id = <comment_id>

Используется таблиц - 2

  • Получение наиболее активных комментаторов
SELECT U.*, count(L.*) as likes_count FROM "Users" U
    INNER JOIN "Likes" L on U.id = L.user_id
    INNER JOIN "Comments" C on L.comment_id = C.id
WHERE C.group_id = <group_id>
GROUP BY U.id
ORDER BY likes_count

Используется таблиц - 3

  • Получение комментариев пользователя
SELECT G.title, C.time, C.text, count(L.*) as likes_count FROM "Comments" C
    INNER JOIN "Groups" G on G.id = C.group_id
    INNER JOIN "Likes" L on C.id = L.comment_id
WHERE C.user_id = <user_id>
GROUP BY G.id, C.id

Используется таблиц - 3

  • Получение друзей пользователя
SELECT U2.first_name, U2.last_name FROM "Friends" F
    INNER JOIN "Users" U1 on F.id1 = U1.id
    INNER JOIN "Users" U2 on F.id2 = U1.id
WHERE F.id1 = <user_id>

Используется таблиц - 3

  • Получение связей по комментариям в одной группе
SELECT U.first_name, U.last_name from "Users" U
    INNER JOIN "Comments" C on U.id = C.user_id
WHERE C.group_id IN(SELECT DISTINCT C2.group_id FROM "Comments" C2 WHERE C2.user_id = 1) AND U.id <> <user_id>

Используется таблиц - 3

  • Число лайков пользователя
SELECT count(*) from "Likes" L
WHERE L.user_id = <user_id>

Используется таблиц - 1

  • Количество комментариев пользователя
SELECT count(*) from "Comments" C
WHERE C.user_id = <user_id>

Используется таблиц - 1

  • Данные для диаграммы количества комментариев по времени
SELECT C.time::date, count(*) FROM "Comments" C
WHERE C.time::date > <dateFrom> AND C.time::date < <dateTo> AND C.time::time > <timeFrom> AND C.time::time < <timeTo> AND C.group_id = <group_id>
GROUP BY C.time::date

Используется таблиц - 1

  • DEBUG: список пользователей
SELECT * FROM "Users" U
WHERE date_part('year', age(U.birthdate)) > <ageFrom> AND date_part('year', age(U.birthdate)) < <ageTo> AND U.first_name LIKE '%<first_name>%' AND U.last_name LIKE '%<last_name>%' AND U.town LIKE '%<town>%'

Используется таблиц - 1

  • DEBUG: список групп
SELECT G.*, count(C.*) as comments_count FROM "Groups" G
    INNER JOIN "Comments" C on G.id = C.group_id
WHERE G.title LIKE '%<title>%' AND G.users_count > <ucFrom> AND G.users_count < <ucTo>
GROUP BY G.id
HAVING count(C.*) > <ccFrom> AND count(C.*) < <ccTo>

Используется таблиц - 2

  • DEBUG: список комментариев
SELECT C.*, U.first_name, U.last_name, count(L.*) as likes_count FROM "Comments" C
    INNER JOIN "Likes" L on C.id = L.comment_id
    INNER JOIN "Users" U on U.id = C.user_id
WHERE U.first_name LIKE '%<first_name>%' AND U.last_name LIKE '%<last_name>%' AND C.text LIKE '%<text>%'
GROUP BY C.id, U.id
HAVING count(L.*) > <lcFrom> AND count(L.*) < <lcTo>

Используется таблиц - 3

Сравнение моделей

Поучили, что объем памяти, используемый нереляционной модели больше чем объем реляционной модели. Так же у нереляционной модели меньше отношение "чистого" и "грязного" объема. Так же получили, что при использовании нерелционной модели при запросах используется меньше коллекций чем таблиц при реляционной модели. Из-за этого запросы при нереляционной модели могут выполнться быстрее. В результате, обе модели имеют свои преимущества. При наличии ограничений следует выбрать или реляционную модель или уменьшить число дублирований в нереляционной. При высоких требованиях к скорости работы следует выбрать нереляционную модель.

Пример хранени данных в БД

Коллекция Users:

[
  {
    "_id": 1,
    "birthdate": {"$date": "1952-05-11T00:00:00.000Z"},
    "first_name": "Douglas",
    "last_name": "Adams",
    "friends": [2],
    "town": "Cambridge"
  }
]

Коллекция Groups:

[
  {
    "_id": 0,
    "title": "Holistic Detective Agency"
    "users_count": 42
  }
]

Коллекция Comments:

[
  {
    "_id": 0,
    "group": {
      "_id": 0,
      "title": "Holistic Detective Agency"
    },
    "likes": [2],
    "text": "Hello World!",
    "time": {"$date": "2021-10-16T11:45:27.461Z"},
    "user": {
      "_id": 1,
      "first_name": "Douglas",
      "last_name": "Adams"
    }
  }
]
⚠️ **GitHub.com Fallback** ⚠️