Skip to content

Модель данных

Leonid Ivashinnikov edited this page Mar 17, 2025 · 22 revisions

Нереляционная модель

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

image

Подробнее...

Оценка объёма информации, хранимой в модели

Users:
 reviews:
 authorId: objectId, V = 12b
 authorName: string, V = 30b
 score: int (int64), V = 8b
 content: string, V = 200b
 createdAt: timestamp, 8b
 Итого: 12 + 30 + 8 + 200 + 8 = 258b

 profiles:
 role: enum, V = 15b
 rating: double, V = 8b
 description: string, V = 500b
 createdAt: timestamp, 8b
 updatedAt: timestamp, 8b
 reviews: array[object], V = NR ⋅ 258b, где NR – количество отзывов
 Итого: 15 + 8 + 500 + 8 + 8 + NR ⋅ 258 = (539 + NR ⋅ 258)b
 В среднем на профиль приходится 10 отзывов, тогда V = 3119b

 users:
 _id: objectId, V = 12b
 displayName: string, V = 30b
 email: string, V = 30b
 password: string, V = 60b
 balance: int (int64), V = 8b
 systemRole: enum, V = 10b
 profiles: array[object], V = NP ⋅ 3119b, где NP – количество профилей. NP = 2 (фрилансер и заказчик), поэтому V = 6238b
 active: bool, V = 1b
 createdAt: timestamp, 8b
 updatedAt: timestamp, 8b

Фактический объём коллекции users: 12 + 30 + 30 + 60 + 8 + 10 + 6238 + 1 + 8 + 8 = 6405b

Orders:
 statuses:
 title: enum, V = 15b
 content: string, V = 100b
 createdAt: timestamp, 8b
 Итого: 15 + 100 + 8 = 123b

 responses:
 freelancerName: string, V = 30b
 freelancerId: objectId, V = 12b
 chatId: objectId, V = 12b
 coverLetter: string, V = 500b
 active: bool, V = 1b
 createdAt: timestamp, 8b
 updatedAt: timestamp, 8b
 Итого: 30 + 12 + 12 + 500 + 1 + 8 + 8 = 571b

 orders:
 _id: objectId, V = 12b
 clientId: objectId, V = 12b
 title: string, 50b
 description: string, 1000b
 completionTime: int (int64), V = 8b
 cost: int (int64), V = 8b
 active: bool, V = 1b
 responses: array[object], V = NRes ⋅ 571b, где NRes – количество откликов. В среднем NRes = 5, поэтому V = 2855b
 freelancerId: objectId, V = 12b
 budget: int (int64), V = 8b
 statuses: array[object], V = NS ⋅ 123b, где NS – количество статусов. В среднем NS = 5, поэтому V = 615b
 createdAt: timestamp, 8b
 updatedAt: timestamp, 8b

Фактический объём коллекции orders: 12 + 12 + 50 + 1000 + 8 + 8 + 1 + 2855 + 12 + 8 + 615 + 8 + 8 = 4597b

Chats:
 client:
 id: objectId, V = 12b
 publicName: string, V = 30b
 Итого: 12 + 30 = 42b

 messages:
 createdAt: timestamp, 8b
 sender: enum, V = 15b
 content: string, V = 500b
 Итого: 8 + 15 + 500 = 523b

 freelancer:
 id: objectId, V = 12b
 publicName: string, V = 30b
 Итого: 12 + 30 = 42b

 chats:
 _id: objectId, V = 12b
 client: object, V = 42b
 orderId: objectId, V = 12b
 active: bool, V = 1b
 freelancer: object, V = 42b
 createdAt: timestamp, 8b
 updatedAt: timestamp, 8b
 messages: array[object], V = NM ⋅ 523b, где M – количество сообщений. В среднем NM = 50, поэтому V = 26150b

Фактический объём коллекции chats: 12 + 42 + 12 + 1 + 42 + 8 + 8 + 26150 = 26275b

Фактический объём модели: NU ⋅ 6405 + NO ⋅ 4597 + NC ⋅ 26275, где NU – количество пользователей, NO – количество заказов, NC – количество чатов.
Выразим всё через количество заказов: на 1 заказ в среднем приходится 0.5 пользователей (так как заказчики размещают и фрилансеры выполняют более одного заказа) и в среднем 3 чата (считая чаты откликов), поэтому:
V(NO) = (86625 ⋅ NO)bytes

Избыточность данных

Для вычисления «чистого» объёма данных исключим из расчетов дублирующуюся и служебную информацию, тогда:
Users:
 reviews:
 score: int (int64), V = 8b
 content: string, V = 200b
 Итого: 200 + 8 = 208b

 profiles:
 role: enum, V = 15b
 rating: double, V = 8b
 description: string, V = 500b
 reviews: array[object], V = NR ⋅ 208b, где NR – количество отзывов, в среднем NR = 10
 Итого: 15 + 8 + 500 + 10 ⋅ 208 = 2603b

 users:
 displayName: string, V = 30b
 email: string, V = 30b
 password: string, V = 60b
 balance: int (int64), V = 8b
 systemRole: enum, V = 10b
 profiles: array[object], V = NP ⋅ 2603b, где NP – количество профилей. NP = 2 (фрилансер и заказчик), поэтому V = 5206b

«Чистый» объём коллекции users: 30 + 30 + 60 + 8 + 10 + 5206 = 5344b

Orders:
 statuses:
 title: enum, V = 15b
 content: string, V = 100b
 Итого: 115b

 responces:
 coverLetter: string, V = 500b
 Итого: 500b

 orders:
 title: string, 50b
 description: string, 1000b
 completionTime: int (int64), V = 8b
 cost: int (int64), V = 8b
 responses: array[object], V = NRes ⋅ 500b, где NRes – количество откликов. В среднем NRes = 5, поэтому V = 2500b
 budget: int (int64), V = 8b
 statuses: array[object], V = NS ⋅ 115b, где NS – количество статусов. В среднем NS = 5, поэтому V = 575b

«Чистый» объём коллекции orders: 50 + 1000 + 8 + 8 + 2500 + 8 + 575 = 4149b

Chats:
 messages:
 sender: enum, V = 15b
 content: string, V = 500b
 Итого: 515b

 chats:
 messages: array[object], V = NM ⋅ 515b, где NM – количество сообщений. В среднем NM = 50, поэтому V = 25750b

«Чистый» объём коллекции chats: 25750b

«Чистый» объём данных: NU ⋅ 5344 + NO ⋅ 4149 + NC ⋅ 25750, где NU – количество пользователей, NO – количество заказов, NC – количество чатов.
Выразим всё через количество заказов:
Vc(NO) = (84071 ⋅ NO)bytes

Избыточность:
R(NO) = V(NO) / Vc(NO) = 86625 ⋅ NO / 84071 ⋅ NO = 1.03

Направление роста модели при увеличении количества объектов каждой сущности

При увеличении количества объектов любой из сущностей модель будет расти линейно.

Примеры данных

Коллекция users

{
  "_id": ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"),
  "displayName": "Иван Иванов",
  "email": "ivanov@example.com",
  "password": "hash1",
  "balance": 1000,
  "systemRole": "user",
  "profiles": [
    {
      "role": "client",
      "rating": 4.5,
      "description": "Заказчик Иван",
      "createdAt": ISODate("2023-10-01T10:00:00Z"),
      "updatedAt": ISODate("2023-10-01T10:00:00Z"),
      "reviews": []
    }
  ],
  "active": true,
  "createdAt": ISODate("2023-10-01T10:00:00Z"),
  "updatedAt": ISODate("2023-10-01T10:00:00Z")
}

Коллекция orders

{
  "_id": ObjectId("64a1b2c3d4e5f6a7b8c9d0e2"),
  "clientId": ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"),
  "title": "Разработка сайта",
  "description": "Создание лендинга",
  "completionTime": 1000000000,
  "cost": 1000,
  "active": true,
  "responses": [
    {
      "freelancerName": "Петр Петров",
      "freelancerId": ObjectId("64a1b2c3d4e5f6a7b8c9d0e3"),
      "chatId": ObjectId("64a1b2c3d4e5f6a7b8c9d0e4"),
      "coverLetter": "Готов взяться на проект!",
      "active": true,
      "createdAt": ISODate("2023-10-01T10:00:00Z"),
      "updatedAt": ISODate("2023-10-01T10:00:00Z")
    }
  ],
  "freelancerId": ObjectId("64a1b2c3d4e5f6a7b8c9d0e3"),
  "budget": 1000,
  "statuses": [
    {
      "title": "beginning",
      "content": "Заказ создан",
      "createdAt": ISODate("2023-10-01T10:00:00Z")
    }
  ],
  "createdAt": ISODate("2023-10-01T10:00:00Z"),
  "updatedAt": ISODate("2023-10-01T10:00:00Z")
}\

Коллекция chats

{
  "_id": ObjectId("64a1b2c3d4e5f6a7b8c9d0e4"),
  "client": {
    "id": ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"),
    "publicName": "Иван Иванов"
  },
  "orderId": ObjectId("64a1b2c3d4e5f6a7b8c9d0e2"),
  "active": true,
  "freelancer": {
    "id": ObjectId("64a1b2c3d4e5f6a7b8c9d0e3"),
    "publicName": "Петр Петров"
  },
  "createdAt": ISODate("2023-10-01T10:00:00Z"),
  "updatedAt": ISODate("2023-10-01T10:00:00Z"),
  "messages": [
    {
      "createdAt": ISODate("2023-10-01T10:00:00Z"),
      "sender": "client",
      "content": "Здравствуйте!"
    },
    {
      "createdAt": ISODate("2023-10-01T10:05:00Z"),
      "sender": "freelancer",
      "content": "Добрый день!"
    }
  ]
}

Примеры запросов

Сценарий: Регистрация пользователя

Основной сценарий:

Проверка уникальности email:

db.users.findOne({ email: "ivanov@example.com" });
  • Количество запросов: 1
  • Задействованные коллекции: users

Создание нового пользователя:

db.users.insertOne({
  displayName: "Иван Иванов",
  email: "ivanov@example.com",
  password: "hash1",
  balance: 1000,
  systemRole: "user",
  profiles: [
    {
      role: "client",
      rating: 4.5,
      description: "Заказчик Иван",
      createdAt: new Date(),
      updatedAt: new Date(),
      reviews: []
    }
  ],
  active: true,
  createdAt: new Date(),
  updatedAt: new Date()
});
  • Количество запросов: 1
  • Задействованные коллекции: users

Итого:

  • Количество запросов: 2
  • Задействованные коллекции: users

Сценарий: Авторизация пользователя

Основной сценарий:

Проверка пользователя:

db.users.findOne({ email: "ivanov@example.com" });
  • Количество запросов: 1
  • Задействованные коллекции: users

Получение профиля пользователя:

db.users.findOne(
  { email: "ivanov@example.com" },
  { "profiles.role": 1 }
);
  • Количество запросов: 1
  • Задействованные коллекции: users

Итого:

  • Количество запросов: 2
  • Задействованные коллекции: users

Сценарий: Редактирование профиля

Основной сценарий:

Обновление данных пользователя:

db.users.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1") },
  { $set: { displayName: "Иван Иванович" } }
);
  • Количество запросов: 1
  • Задействованные коллекции: users

Обновление данных профиля:

db.users.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"), "profiles.role": "client" },
  { $set: { "profiles.$.description": "Новое описание" } }
);
  • Количество запросов: 1
  • Задействованные коллекции: users

Итого:

  • Количество запросов: 2
  • Задействованные коллекции: users

Сценарий: Смена роли

Основной сценарий:

Обновление роли в профиле:

db.users.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"), "profiles.role": "client" },
  { $set: { "profiles.$.role": "freelancer" } }
);
  • Количество запросов: 1
  • Задействованные коллекции: users

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: users

Сценарий: Заказчик публикует новый заказ

Основной сценарий:

Создание заказа:

db.orders.insertOne({
  clientId: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"),
  title: "Разработка сайта",
  description: "Создание лендинга",
  completionTime: 1000000000,
  cost: 1000,
  active: true,
  responses: [],
  statuses: [
    {
      title: "beginning",
      content: "Заказ создан",
      createdAt: new Date()
    }
  ],
  createdAt: new Date(),
  updatedAt: new Date()
});
  • Количество запросов: 1
  • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: orders

Сценарий: Заказчик редактирует заказ

Основной сценарий:

Обновление данных заказа:

db.orders.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e2") },
  { $set: { title: "Обновленный проект", description: "Новое описание" } }
);
  • Количество запросов: 1
  • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: orders

Сценарий: Стороны согласовали условия и работа началась

Основной сценарий:

Обновление статуса заказа:

db.orders.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e2") },
  { $push: { statuses: { title: "negotiation", content: "Условия согласованы", createdAt: new Date() } } }
);
  • Количество запросов: 1
  • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: orders

Сценарий: Стороны общаются в процессе выполнения заказа

Основной сценарий:

Добавление сообщения в чат:

db.chats.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e4") },
  { $push: { messages: { sender: "client", content: "Здравствуйте!", createdAt: new Date() } } }
);
  • Количество запросов: 1
  • Задействованные коллекции: chats

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: chats

Сценарий: Завершение заказа

Основной сценарий:

Обновление статуса заказа:

db.orders.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e2") },
  { $push: { statuses: { title: "finished", content: "Заказ завершен", createdAt: new Date() } } }
);
  • Количество запросов: 1
  • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: orders

Сценарий: Стороны оставляют отзывы

Основной сценарий:

Добавление отзыва:

db.users.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e3"), "profiles.role": "freelancer" },
  { $push: { "profiles.$.reviews": { authorId: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"), authorName: "Иван Иванов", score: 5, content: "Отлично!", createdAt: new Date() } } }
);
  • Количество запросов: 1
  • Задействованные коллекции: users

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: users

Сценарий: Исполнитель смотрит свои отклики

Основной сценарий:

Получение откликов исполнителя:

db.orders.find({ "responses.freelancerId": ObjectId("64a1b2c3d4e5f6a7b8c9d0e3") });
  • Количество запросов: 1
  • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: orders

Сценарий: Исполнитель отзывает отклик

Основной сценарий:

Обновление активности отклика:

db.orders.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e2"), "responses.freelancerId": ObjectId("64a1b2c3d4e5f6a7b8c9d0e3") },
  { $set: { "responses.$.active": false } }
);
  • Количество запросов: 1
  • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: orders

Сценарий: Исполнитель общается в чате до согласования

Основной сценарий:

Добавление сообщения в чат:

db.chats.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e4") },
  { $push: { messages: { sender: "freelancer", content: "Добрый день!", createdAt: new Date() } } }
);
  • Количество запросов: 1
  • Задействованные коллекции: chats

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: chats

Сценарий: Заказчик согласовывает условия в чате

Основной сценарий:

Добавление системного сообщения о согласовании:

db.chats.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e4") },
  { $push: { messages: { sender: "system", content: "Условия согласованы.", createdAt: new Date() } } }
);
  • Количество запросов: 1
  • Задействованные коллекции: chats

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: chats

Сценарий: Исполнитель согласовывает условия в чате

Основной сценарий:

Добавление системного сообщения о согласовании:

db.chats.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e4") },
  { $push: { messages: { sender: "system", content: "Условия согласованы.", createdAt: new Date() } } }
);
  • Количество запросов: 1
  • Задействованные коллекции: chats

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: chats

Сценарий: Подсчёт статистики

Основной сценарий:

Получение количества заказов:

db.orders.countDocuments();
  • Количество запросов: 1
  • Задействованные коллекции: orders

Получение количества пользователей:

db.users.countDocuments();
  • Количество запросов: 1
  • Задействованные коллекции: users

Получение количества споров:

db.chats.countDocuments({ "messages.sender": "system", "messages.content": { $regex: /Спор/ } });
  • Количество запросов: 1
  • Задействованные коллекции: chats

Итого:

  • Количество запросов: 3
  • Задействованные коллекции: orders, users, chats

Сценарий: Исполнитель ищет заказы

Основной сценарий:

Получение списка активных заказов:

db.orders.find({ active: true });
  • Количество запросов: 1
  • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: orders

Сценарий: Исполнитель откликается на заказ

Основной сценарий:

Создание отклика:

db.orders.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e2") },
  {
    $push: {
      responses: {
        freelancerName: "Петр Петров",
        freelancerId: ObjectId("64a1b2c3d4e5f6a7b8c9d0e3"),
        chatId: ObjectId("64a1b2c3d4e5f6a7b8c9d0e4"),
        coverLetter: "Готов взяться на проект!",
        active: true,
        createdAt: new Date(),
        updatedAt: new Date()
      }
    }
  }
);
  • Количество запросов: 1
  • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: orders

Сценарий: Исполнитель выводит средства

Основной сценарий:

Обновление баланса пользователя:

db.users.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e3") },
  { $inc: { balance: -500 } }
);
  • Количество запросов: 1
  • Задействованные коллекции: users

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: users

Сценарий: Экспорт данных

Основной сценарий:

Экспорт данных:

db.users.find();
db.profiles.find();
db.orders.find();
db.chats.find();
db.messages.find();
db.responses.find();
db.reviews.find();
db.statuses.find();
  • Количество запросов: 8
  • Задействованные коллекции: Все коллекции

Итого:

  • Количество запросов: 8
  • Задействованные коллекции: Все коллекции

Сценарий: Импорт данных

Основной сценарий:

Импорт данных:

db.users.insertOne({
  displayName: "Новый Пользователь",
  email: "newuser@example.com",
  password: "hash4",
  balance: 0,
  systemRole: "user",
  profiles: [],
  active: true,
  createdAt: new Date(),
  updatedAt: new Date()
});
  • Количество запросов: 1
  • Задействованные коллекции: users

Итого:

  • Количество запросов: 1
  • Задействованные коллекции: users

Реляционная модель

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

image

Подробнее...

Оценка объёма информации, хранимой в модели

Users:
 reviews:
 id: serial, V = 4b
 author_id: integer, V = 4b
 profile_id: integer, V = 4b
 score: enum, V = 1b
 content: text, V = 200b
 created_at: timestamp, V = 8b
 Итого: 4 + 4 + 4 + 1 + 200 + 8 = 221b

 profiles:
 id: serial, V = 4b
 user_id: integer, V = 4b
 role: enum, V = 15b
 rating: real, V = 4b
 description: text, V = 500b
 created_at: timestamp, V = 8b
 updated_at: timestamp, V = 8b
 Итого: 4 + 4 + 15 + 4 + 500 + 8 + 8 = 543b

 users:
 id: serial, V = 4b
 email: varchar(256), V = 30b
 public_name: varchar(64), V = 30b
 password: varchar(256), V = 60b
 balance: integer, V = 4b
 system_role: enum, V = 15b
 is_active: boolean, V = 1b
 created_at: timestamp, V = 8b
 updated_at: timestamp, V = 8b
 Итого: 4 + 30 + 30 + 60 + 4 + 15 + 1 + 8 + 8 = 160b

Фактический объём users: NR ⋅ 221 + NP ⋅ 543 + NU ⋅ 160, где NR – количество отзывов, NP – количество профилей, NU – количество пользователей.
Так как в среднем на профиль приходится 10 отзывов, а на пользователя – 2 профиля (фрилансер и заказчик), то объём: NU ⋅ (20 ⋅ 221 + 2 ⋅ 543 + 160) = (NU ⋅ 5666)b

Chats:
 messages:
 id: serial, V = 4b
 chat_id: integer, V = 4b
 sender_id: integer, V = 4b
 is_system: boolean, V = 1b
 text: text, V = 500b
 created_at: timestamp, V = 8b
 Итого: 4 + 4 + 4 + 1 + 500 + 8 = 521b

 chats:
 id: serial, V = 4b
 order_id: integer, V = 4b
 client_id: integer, V = 4b
 freelancer_id: integer, V = 4b
 is_active: boolean, V = 1b
 created_at: timestamp, V = 8b
 updated_at: timestamp, V = 8b
 Итого: 4 + 4 + 4 + 4 + 1 + 8 + 8 = 33b

Фактический объём chats: NM ⋅ 521 + NC ⋅ 33, где NM – количество сообщений, NC – количество чатов.
Так как в среднем на чат приходится 50 сообщений, то объём: NC ⋅ (50 ⋅ 521 + 33) = (NC ⋅ 26083)b

Orders:
 statuses:
 order_id: integer, V = 4b
 sequential_number: integer, V = 4b
 title: enum, V = 15b
 extra_info: jsonb, V = 20b
 created_at: timestamp, V = 8b
 Итого: 4 + 4 + 15 + 20 + 8 = 51b

 responses:
 order_id: integer, V = 4b
 freelancer_id: integer, V = 4b
 chat_id: integer, V = 4b
 is_active: boolean, V = 1b
 created_at: timestamp, V = 8b
 updated_at: timestamp, V = 8b
 Итого: 4 + 4 + 4 + 1 + 8 + 8 = 29b

 orders:
 id: serial, V = 4b
 client_id: integer, V = 4b
 title: varchar(128), V = 50b
 description: text, V = 1000b
 completion_time: bigint, V = 8b
 cost: integer, V = 4b
 is_active: boolean, V = 1b
 freelancer_id: integer, V = 4b
 budget: integer, V = 4b
 created_at: timestamp, V = 8b
 updated_at: timestamp, V = 8b
 Итого: 4 + 4 + 50 + 1000 + 8 + 4 + 1 + 4 + 4 + 8 + 8 = 1095b

Фактический объём orders: NS ⋅ 51 + NRes ⋅ 29 + NO ⋅ 1095, где NS – количество статусов, NRes – количество откликов, NO – количество заказов.
Так как в среднем на заказ приходится 5 откликов и 5 статусов, то объём: NO ⋅ (5 ⋅ 51 + 5 ⋅ 29 + 1095) = (NO ⋅ 1495)b

Фактический объём модели: NU ⋅ 5666 + NO ⋅ 1495 + NC ⋅ 26083, где NU – количество пользователей, NO – количество заказов, NC – количество чатов.
Выразим всё через количество заказов аналогично нереляционной модели:
V(NO) = (82577 ⋅ NO)bytes

Избыточность данных

Для вычисления «чистого» объёма данных исключим из расчетов дублирующуюся и служебную информацию, тогда:

Users:
 reviews:
 score: enum, V = 1b
 content: text, V = 200b
 Итого: 201b

 profiles:
 role: enum, V = 15b
 rating: real, V = 4b
 description: text, V = 500b
 Итого: 519b

 users:
 email: varchar(256), V = 30b
 public_name: varchar(64), V = 30b
 password: varchar(256), V = 60b
 balance: integer, V = 4b
 system_role: enum, V = 15b
 Итого: 30 + 30 + 60 + 4 + 15 = 139b

«Чистый» объём users: NR ⋅ 201 + NP ⋅ 519 + NU ⋅ 139, где NR – количество отзывов, NP – количество профилей, NU – количество пользователей.
Так как в среднем на профиль приходится 10 отзывов, а на пользователя – 2 профиля (фрилансер и заказчик), то объём: NU ⋅ (20 ⋅ 201 + 2 ⋅ 519 + 139) = (NU ⋅ 5197)b

Chats:
 messages:
 is_system: boolean, V = 1b
 text: text, V = 500b
 Итого: 501b

 chats:
 is_active: boolean, V = 1b
 Итого: 1b

«Чистый» объём chats: NM ⋅ 501 + NC ⋅ 1, где NM – количество сообщений, NC – количество чатов.
Так как в среднем на чат приходится 50 сообщений, то объём: NC ⋅ (50 ⋅ 501 + 1) = (NC ⋅ 25051)b

Orders:
 statuses:
 title: enum, V = 15b
 extra_info: jsonb, V = 20b
 Итого: 35b

 responses:
 is_active: boolean, V = 1b
 Итого: 1b

 orders:
 title: varchar(128), V = 50b
 description: text, V = 1000b
 completion_time: bigint, V = 8b
 cost: integer, V = 4b
 budget: integer, V = 4b
 Итого: 50 + 1000 + 8 + 4 + 4 = 1066b

«Чистый» объём orders: NS ⋅ 35 + NRes ⋅ 1 + NO ⋅ 1066, где NS – количество статусов, NRes – количество откликов, NO – количество заказов.
Так как в среднем на заказ приходится 5 откликов и 5 статусов, то объём: NO ⋅ (5 ⋅ 35 + 5 ⋅ 1 + 1066) = (NO ⋅ 1246)b

«Чистый» объём модели: NU ⋅ 5197 + NO ⋅ 1246 + NC ⋅ 25051, где NU – количество пользователей, NO – количество заказов, NC – количество чатов.
Выразим всё через количество заказов аналогично нереляционной модели:
Vc(NO) = (78998 ⋅ NO)bytes

Избыточность:
R(NO) = V(NO) / Vc(NO) = 82577 / 78998 = 1.045

Направление роста модели при увеличении количества объектов каждой сущности

При увеличении количества объектов любой из сущностей модель будет расти линейно.

Примеры данных

Таблица users

id email public_name password balance system_role is_active created_at updated_at
1 ivanov@example.com Иван Иванов hash1 1000 user true 2023-10-01 10:00:00 2023-10-01 10:00:00
2 petrov@example.com Петр Петров hash2 500 user true 2023-10-01 10:05:00 2023-10-01 10:05:00
3 admin@example.com Админ Админов hash3 0 admin true 2023-10-01 10:10:00 2023-10-01 10:10:00

Таблица profiles

id user_id role rating description created_at updated_at
1 1 client 4.5 Заказчик Иван 2023-10-01 10:00:00 2023-10-01 10:00:00
2 2 freelancer 4.7 Фрилансер Петр 2023-10-01 10:05:00 2023-10-01 10:05:00

Таблица orders

id client_id title description completion_time cost is_active freelancer_id budget created_at updated_at
1 1 Разработка сайта Создание лендинга 1000000000 1000 true 2 1000 2023-10-01 10:00:00 2023-10-01 10:00:00

Таблица chats

id order_id client_id freelancer_id is_active created_at updated_at
1 1 1 2 true 2023-10-01 10:00:00 2023-10-01 10:00:00

Таблица messages

id chat_id sender_id is_system text created_at
1 1 1 false Здравствуйте! 2023-10-01 10:00:00
2 1 2 false Добрый день! 2023-10-01 10:05:00

Таблица responses

order_id freelancer_id chat_id is_active created_at updated_at
1 2 1 true 2023-10-01 10:00:00 2023-10-01 10:00:00

Таблица reviews

id author_id profile_id score content created_at
1 1 2 5 Отлично! 2023-10-01 10:00:00

Таблица statuses

order_id sequential_number title extra_info created_at
1 1 beginning {} 2023-10-01 10:00:00

Примеры запросов

Сценарий: Регистрация пользователя

Основной сценарий:

  1. Проверка уникальности email:

    SELECT id FROM users WHERE email = 'ivanov@example.com';

    • Количество запросов: 1

    • Задействованные коллекции: users

  2. Создание нового пользователя:

    INSERT INTO users (email, public_name, password, system_role, is_active) VALUES ('ivanov@example.com', 'Иван Иванов', 'hash1', 'user', true);

    • Количество запросов: 1

    • Задействованные коллекции: users

  3. Создание профиля пользователя:

    INSERT INTO profiles (user_id, role, description) VALUES (1, 'client', 'Заказчик Иван');

    • Количество запросов: 1

    • Задействованные коллекции: profiles

Итого:

  • Количество запросов: 3

  • Задействованные коллекции: usersprofiles

Сценарий: Авторизация пользователя

Основной сценарий:

  1. Проверка пользователя:

    SELECT id, password FROM users WHERE email = 'ivanov@example.com';

    • Количество запросов: 1

    • Задействованные коллекции: users

  2. Получение профиля пользователя:

    SELECT role FROM profiles WHERE user_id = 1;

    • Количество запросов: 1

    • Задействованные коллекции: profiles

Итого:

  • Количество запросов: 2

  • Задействованные коллекции: usersprofiles

Сценарий: Редактирование профиля

Основной сценарий:

  1. Обновление данных пользователя:

    UPDATE users SET public_name = 'Иван Иванович' WHERE id = 1;

    • Количество запросов: 1

    • Задействованные коллекции: users

  2. Обновление данных профиля:

    UPDATE profiles SET description = 'Новое описание' WHERE user_id = 1;

    • Количество запросов: 1

    • Задействованные коллекции: profiles

Итого:

  • Количество запросов: 2

  • Задействованные коллекции: usersprofiles

Сценарий: Смена роли

Основной сценарий:

  1. Обновление роли в профиле:

    UPDATE profiles SET role = 'freelancer' WHERE user_id = 1;

    • Количество запросов: 1

    • Задействованные коллекции: profiles

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: profiles

Сценарий: Заказчик публикует новый заказ

Основной сценарий:

  1. Создание заказа:

    INSERT INTO orders (client_id, title, description, completion_time, cost, is_active) VALUES (1, 'Разработка сайта', 'Создание лендинга', 1000000000, 1000, true);

    • Количество запросов: 1

    • Задействованные коллекции: orders

  2. Создание статуса заказа:

    INSERT INTO statuses (order_id, sequential_number, title) VALUES (1, 1, 'beginning');

    • Количество запросов: 1

    • Задействованные коллекции: statuses

Итого:

  • Количество запросов: 2

  • Задействованные коллекции: ordersstatuses

Сценарий: Заказчик редактирует заказ

Основной сценарий:

  1. Обновление данных заказа:

    UPDATE orders SET title = 'Обновленный проект', description = 'Новое описание' WHERE id = 1;

    • Количество запросов: 1

    • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: orders

Сценарий: Стороны согласовали условия и работа началась

Основной сценарий:

  1. Обновление статуса заказа:

    INSERT INTO statuses (order_id, sequential_number, title) VALUES (1, 2, 'negotiation');

    • Количество запросов: 1

    • Задействованные коллекции: statuses

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: statuses

Сценарий: Стороны общаются в процессе выполнения заказа

Основной сценарий:

  1. Добавление сообщения в чат:

    INSERT INTO messages (chat_id, sender_id, text) VALUES (1, 1, 'Здравствуйте!');

    • Количество запросов: 1

    • Задействованные коллекции: messages

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: messages

Сценарий: Завершение заказа

Основной сценарий:

  1. Обновление статуса заказа:

    INSERT INTO statuses (order_id, sequential_number, title) VALUES (1, 3, 'finished');

    • Количество запросов: 1

    • Задействованные коллекции: statuses

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: statuses

Сценарий: Стороны оставляют отзывы

Основной сценарий:

  1. Добавление отзыва:

    INSERT INTO reviews (author_id, profile_id, score, content) VALUES (1, 2, 5, 'Отлично!');

    • Количество запросов: 1

    • Задействованные коллекции: reviews

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: reviews

Сценарий: Исполнитель смотрит свои отклики

Основной сценарий:

  1. Получение откликов исполнителя:

    SELECT * FROM responses WHERE freelancer_id = 2;

    • Количество запросов: 1

    • Задействованные коллекции: responses

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: responses

Сценарий: Исполнитель отзывает отклик

Основной сценарий:

  1. Обновление активности отклика:

    UPDATE responses SET is_active = false WHERE order_id = 1 AND freelancer_id = 2;

    • Количество запросов: 1

    • Задействованные коллекции: responses

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: responses

Сценарий: Исполнитель общается в чате до согласования

Основной сценарий:

  1. Добавление сообщения в чат:

    INSERT INTO messages (chat_id, sender_id, text) VALUES (1, 2, 'Добрый день!');

    • Количество запросов: 1

    • Задействованные коллекции: messages

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: messages

Сценарий: Заказчик согласовывает условия в чате

Основной сценарий:

  1. Добавление системного сообщения о согласовании:

    INSERT INTO messages (chat_id, is_system, text) VALUES (1, true, 'Условия согласованы.');

    • Количество запросов: 1

    • Задействованные коллекции: messages

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: messages

Сценарий: Исполнитель согласовывает условия в чате

Основной сценарий:

  1. Добавление системного сообщения о согласовании:

    INSERT INTO messages (chat_id, is_system, text) VALUES (1, true, 'Условия согласованы.');

    • Количество запросов: 1

    • Задействованные коллекции: messages

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: messages

Сценарий: Подсчёт статистики

Основной сценарий:

  1. Получение количества заказов:

    SELECT COUNT(*) FROM orders;

    • Количество запросов: 1

    • Задействованные коллекции: orders

  2. Получение количества пользователей:

    SELECT COUNT(*) FROM users;

    • Количество запросов: 1

    • Задействованные коллекции: users

  3. Получение количества споров:

    SELECT COUNT(*) FROM messages WHERE is_system = true AND text LIKE '%Спор%';

    • Количество запросов: 1

    • Задействованные коллекции: messages

Итого:

  • Количество запросов: 3

  • Задействованные коллекции: ordersusersmessages

Сценарий: Исполнитель ищет заказы

Основной сценарий:

  1. Получение списка активных заказов:

    SELECT * FROM orders WHERE is_active = true;

    • Количество запросов: 1

    • Задействованные коллекции: orders

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: orders

Сценарий: Исполнитель откликается на заказ

Основной сценарий:

  1. Создание отклика:

    INSERT INTO responses (order_id, freelancer_id, chat_id, is_active) VALUES (1, 2, 1, true);

    • Количество запросов: 1

    • Задействованные коллекции: responses

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: responses

Сценарий: Исполнитель выводит средства

Основной сценарий:

  1. Обновление баланса пользователя:

    UPDATE users SET balance = balance - 500 WHERE id = 2;

    • Количество запросов: 1

    • Задействованные коллекции: users

Итого:

  • Количество запросов: 1

  • Задействованные коллекции: users

Сценарий: Экспорт данных

Основной сценарий:

  1. Экспорт данных:

    SELECT * FROM users; SELECT * FROM profiles; SELECT * FROM orders; SELECT * FROM chats; SELECT * FROM messages; SELECT * FROM responses; SELECT * FROM reviews; SELECT * FROM statuses;

    • Количество запросов: 8

    • Задействованные коллекции: Все таблицы

Итого:

  • Количество запросов: 8

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

Удельный объём информации

Параметр Нереляционная модель Реляционная модель
"Грязный" объём 86625 ⋅ NO 82577 ⋅ NO
"Чистый" объём 84071 ⋅ NO 78998 ⋅ NO
Избыточность 1.03 1.045

Запросы по отдельным юзкейсам

Название Нереляционная, запросы Реляционная, запросы Нереляционная, коллекции Реляционная, коллекции
1 Регистрация пользователя 2 3 1 (users) 2 (users, profiles)
2 Авторизация пользователя 2 2 1 (users) 2 (users, profiles)
3 Редактирование профиля 1 2 1 (users) 2 (users, profiles)
4 Смена роли 1 1 1 (users) 1 (profiles)
5 Заказчик публикует новый заказ 1 2 1 (orders) 2 (orders, statuses)
6 Заказчик редактирует заказ 1 1 1 (orders) 1 (orders)
7 Стороны согласовали условия и работа началась 1 1 1 (orders) 1 (statuses)
8 Стороны общаются в процессе выполнения заказа 1 1 1 (orders) 1 (messages)
9 Завершение заказа 1 1 1 (orders) 1 (statuses)
10 Стороны оставляют отзывы 1 1 1 (users) 1 (reviews)
11 Исполнитель смотрит свои отклики 1 1 1 (orders) 1 (responses)
12 Исполнитель отзывает отклик 1 1 1 (orders) 1 (responses)
13 Исполнитель общается в чате до согласования 1 1 1 (chats) 1 (messages)
14 Заказчик согласовывает условия в чате 1 1 1 (chats) 1 (messages)
15 Исполнитель согласовывает условия в чате 1 1 1 (chats) 1 (messages)
16 Подсчёт статистики 3 3 3 (orders, users, chats) 3 (orders, users, messages)
17 Исполнитель ищет заказы 1 1 1 (orders) 1 (orders)
18 Исполнитель откликается на заказ 1 1 1 (orders) 1 (responses)
19 Исполнитель выводит средства 1 1 1 (users) 1 (users)

Вывод

Нереляционная модель требует больше объёма, чем реляционная. Количество запросов к нереляционной немного меньше, чем к реляционной (в основном по всем юзкейсам требуется один запрос, кроме регистрации/авторизации). Избыточность у реляционной немногим меньше, чем у нереляционной. В целом, что SQL, что noSQL одинаково хорошо подходят к задаче. Здесь мало слабоструктурированных данных (только сообщения в переписке).

Clone this wiki locally