Модель данных - moevm/nosql2h21-vk-comments GitHub Wiki
БД содержит 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
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"
}
}
]