Модель данных
1. Определение сущностей, которые необходимо хранить
1.1. Перечень сущностей и ключевые атрибуты
Пользователь
Что хранит: данные учётной записи и роль пользователя в системе.
| Атрибут | Что это и зачем нужно |
|---|---|
userId | внутренний идентификатор пользователя |
email | email для входа, если используется email-авторизация |
phone | телефон для входа, если используется телефон |
passwordHash | хэш пароля вместо хранения пароля в открытом виде |
displayName | отображаемое имя пользователя |
role | роль в системе, например user или moderator |
status | состояние учётной записи, например active, blocked |
createdAt | дата и время создания аккаунта |
lastLoginAt | дата и время последнего входа |
Согласие пользователя
Что хранит: факт принятия обязательных документов с указанием версии.
| Атрибут | Что это и зачем нужно |
|---|---|
consentId | идентификатор записи согласия |
userId | пользователь, который принял документ |
documentType | тип документа, например privacy_policy или user_agreement |
documentVersion | версия документа на момент принятия |
acceptedAt | дата и время принятия |
Сессия / токен доступа / токен сброса
Что хранит: временные данные для авторизации, восстановления доступа и защиты от перебора паролей.
| Атрибут | Что это и зачем нужно |
|---|---|
sessionId | идентификатор сессии |
userId | пользователь, которому принадлежит сессия или токен |
tokenHash | хэш токена |
tokenType | тип токена: access, reset и т.д. |
expiresAt | срок действия токена |
failedLoginCount | число неуспешных попыток входа |
blockedUntil | время, до которого вход временно заблокирован |
createdAt | момент создания записи |
Категория
Что хранит: справочник категорий для фильтров и формы создания объявления.
| Атрибут | Что это и зачем нужно |
|---|---|
categoryId | идентификатор категории |
name | название категории |
isActive | доступна ли категория для использования |
Объявление
Что хранит: основные данные объявления, которые используются в ленте, карточке, фильтрации и сопоставлении.
| Атрибут | Что это и зачем нужно |
|---|---|
adId | идентификатор объявления |
authorId | пользователь, который создал объявление |
type | тип объявления: lost или found |
status | статус объявления: active или closed |
categoryId | ссылка на категорию |
eventAt | дата и время события потери или находки |
description | текстовое описание из формы объявления |
locationCity | город |
locationDistrict | район |
locationAddressText | текстовое описание места |
locationSource | способ указания места: map или manual |
locationLatitude | широта, если место указано на карте |
locationLongitude | долгота, если место указано на карте |
structuredAttributes | структурированные признаки для поиска и сопоставления, например цвет, бренд, модель, тип вещи, порода, комплектность |
createdAt | дата и время создания объявления |
closedAt | дата и время закрытия объявления |
Фото
Что хранит: запись о фото на уровне приложения. Сама бинарная фотография хранится отдельно в объектном хранилище.
| Атрибут | Что это и зачем нужно |
|---|---|
photoId | идентификатор фото, который возвращается после загрузки |
adId | идентификатор объявления, к которому прикреплено фото; до публикации может быть пустым |
objectKey | ключ объекта в object storage |
contentType | MIME-тип файла, например image/jpeg |
sizeBytes | размер файла в байтах |
uploadedAt | дата и время загрузки |
status | состояние фото, например temporary, attached. |
Отклик
Что хранит: факт отклика пользователя на объявление.
| Атрибут | Что это и зачем нужно |
|---|---|
responseId | идентификатор отклика |
adId | объявление, на которое откликнулись |
userId | пользователь, который откликнулся |
createdAt | дата и время создания отклика |
status | состояние отклика |
Диалог
Что хранит: канал общения между сторонами после отклика.
| Атрибут | Что это и зачем нужно |
|---|---|
dialogId | идентификатор диалога |
adId | объявление, по которому создан диалог |
responseId | отклик, на основе которого создан диалог |
createdByUserId | инициатор диалога |
participantUserId | второй участник диалога |
status | состояние диалога, например owner_check_pending, open, closed |
createdAt | дата и время создания |
updatedAt | время последнего изменения |
Сообщение
Что хранит: единичное сообщение внутри диалога.
| Атрибут | Что это и зачем нужно |
|---|---|
messageId | идентификатор сообщения |
dialogId | диалог, к которому относится сообщение |
senderId | отправитель |
text | текст сообщения |
createdAt | дата и время отправки |
isSystem | признак системного сообщения |
Owner-check
Что хранит: сам процесс проверки владельца.
| Атрибут | Что это и зачем нужно |
|---|---|
ownerCheckId | идентификатор проверки |
dialogId | диалог, внутри которого идёт проверка |
initiatorUserId | кто запустил проверку |
targetUserId | кто отвечает на вопросы |
status | текущий статус проверки |
startedAt | время начала проверки |
decidedAt | время принятия решения |
decidedBy | кто принял решение по результату проверки |
Вопрос owner-check
Что хранит: контрольный вопрос в рамках owner-check.
| Атрибут | Что это и зачем нужно |
|---|---|
questionId | идентификатор вопроса |
ownerCheckId | ссылка на процесс owner-check |
authorId | кто задал вопрос |
questionText | текст вопроса |
createdAt | дата и время создания |
Ответ owner-check
Что хранит: ответ на контрольный вопрос.
| Атрибут | Что это и зачем нужно |
|---|---|
answerId | идентификатор ответа |
questionId | вопрос, на который дан ответ |
ownerCheckId | ссылка на процесс owner-check |
authorId | кто дал ответ |
answerText | текст ответа |
createdAt | дата и время создания |
Жалоба
Что хранит: жалобу на объявление и информацию о её обработке.
| Атрибут | Что это и зачем нужно |
|---|---|
reportId | идентификатор жалобы |
reporterUserId | кто отправил жалобу |
targetType | тип объекта жалобы |
targetId | идентификатор объекта жалобы |
reason | причина жалобы |
comment | комментарий к жалобе |
status | состояние жалобы |
createdAt | дата и время отправки |
resolvedAt | дата и время обработки |
moderatorId | модератор, который обработал жалобу |
Результат сопоставления
Что хранит: производную запись о том, что два объявления были сопоставлены системой.
| Атрибут | Что это и зачем нужно |
|---|---|
matchId | идентификатор результата сопоставления |
sourceAdId | объявление, для которого считалось сопоставление |
candidateAdId | объявление-кандидат |
score | числовая оценка совпадения |
reasonSummary | краткое объяснение, почему найдено совпадение |
calculatedAt | дата и время расчёта |
Событие аналитики
Что хранит: пользовательское или системное событие для аналитики продукта.
| Атрибут | Что это и зачем нужно |
|---|---|
eventId | идентификатор события |
eventName | название события |
userId | пользователь, если он авторизован |
authSessionId | идентификатор авторизационной сессии, только для авторизованных пользователей |
entityType | тип объекта, к которому относится событие |
entityId | идентификатор объекта |
occurredAt | дата и время события |
payload | дополнительные параметры события |
Аудит / журнал безопасности
Что хранит: критичные действия и security-события.
| Атрибут | Что это и зачем нужно |
|---|---|
auditId | идентификатор записи |
eventType | тип события |
userId | пользователь, который инициировал действие |
targetType | тип объекта действия |
targetId | идентификатор объекта |
result | результат действия |
ip | IP-адрес |
userAgent | user-agent клиента |
createdAt | дата и время события |
1.4. Взаимодействие с сущностями и характер данных
| Сущность | Кто взаимодействует | Характер данных |
|---|---|---|
| Пользователь | пользователь, система, модератор | транзакционные |
| Согласие пользователя | пользователь, система | транзакционные |
| Сессия / токен доступа / токен сброса | пользователь, система | транзакционные временные |
| Категория | гость, пользователь, система | справочные |
| Объявление | гость, пользователь, система, модератор | транзакционные + аналитические |
| Фото | пользователь, система, модератор | транзакционные |
| Отклик | пользователь, система | транзакционные |
| Диалог | пользователь, система, модератор (по жалобе) | транзакционные |
| Сообщение | пользователь, система | транзакционные |
| Owner-check | пользователь, система | транзакционные |
| Вопрос owner-check | пользователь, система | транзакционные |
| Ответ owner-check | пользователь, система | транзакционные |
| Жалоба | пользователь, система, модератор | транзакционные |
| Результат сопоставления | система, пользователь | операционные |
| Событие аналитики | система, аналитик / PO | аналитические |
| Аудит / журнал безопасности | система, модератор | транзакционные + аналитические |
2. Какие акторы взаимодействуют с данными
Основные акторы
-
Гость
- читает ленту, карту и карточки объявлений.
-
Авторизованный пользователь
-
регистрируется и входит;
-
принимает документы;
-
создаёт объявления;
-
загружает фото;
-
откликается на объявление;
-
участвует в диалоге;
-
проходит owner-check;
-
отправляет жалобы;
-
закрывает свои объявления.
-
-
Модератор
-
работает с жалобами;
-
просматривает объявление, на которое пожаловались;
-
просматривает профиль автора объявления;
-
при необходимости проверяет связанное фото;
-
при необходимости проверяет связанный диалог;
-
скрывает объявление или ограничивает аккаунт.
-
-
Система
-
валидирует данные;
-
создаёт отклики, диалоги и системные записи;
-
считает совпадения;
-
связывает фото с объявлениями;
-
фиксирует аналитические события и аудит.
-
-
Аналитик / PO
- работает с аналитическими данными и KPI.
3. Характер взаимодействия с данными
3.1. Транзакционные данные
К транзакционным относятся данные, которые участвуют в основных пользовательских операциях и должны обновляться корректно и согласованно.
К транзакционным относятся:
-
пользователи;
-
согласия пользователя;
-
сессии и токены;
-
категории;
-
объявления;
-
фото;
-
отклики;
-
диалоги;
-
сообщения;
-
owner-check, вопросы и ответы;
-
жалобы;
-
аудит / журнал безопасности.
Почему это OLTP: эти данные нужны для ежедневной работы системы: создания, чтения, изменения и закрытия записей. Для них важны целостность, корректные статусы, связи между данными и быстрые короткие операции.
3.2. Аналитические данные
К аналитическим относятся данные, которые нужны для анализа продукта, безопасности и качества сервиса.
К аналитическим относятся:
-
события поведения пользователей;
-
технические логи;
-
объявления, как источник продуктовой аналитики;
-
аудит / журнал безопасности;
-
агрегаты KPI и витрины отчётности.
Почему это аналитические данные: они накапливаются и затем используются для агрегирования, отчётности и анализа. Для них важнее история, группировки и метрики, чем мгновенное транзакционное обновление. Объявления тоже используются для анализа, распределения по категориям, районам, типам, доли закрытых объявлений и тд.
4. Определение подходящих технологий хранения
4.1. Исходные ожидания по нагрузке для выбора технологий
Так как MVP ещё не запущен, для выбора технологий хранения в документе используется оценка нагрузки с запасом относительно минимальных требований MVP.
Для проектирования есть следующие ориентиры:
-
до 500 одновременных пользователей в пиковые периоды;
-
до 250 RPS на чтение для ленты, поиска, карточек и карты;
-
до 50 RPS на запись для создания объявлений, загрузки фото, откликов, сообщений и жалоб;
-
до 1 000–3 000 новых объявлений в месяц;
-
до 5 фото на одно объявление;
4.2. Таблица выбора технологий хранения по сущностям
| Сущность / данные | Ожидаемый объём и рост | Как используются данные | Ключевые требования | Итоговое решение |
|---|---|---|---|---|
| Пользователь | 5 000–20 000 записей в год | чтение по userId, email, phone; редкие изменения | сильная консистентность, уникальность, транзакции | реляционная БД |
| Согласие пользователя | 10 000–40 000 записей в год | запись при регистрации и принятии документов, чтение редко | сильная консистентность, хранение версии документа | реляционная БД |
| Сессия / токен доступа / токен сброса | 10 000–100 000 записей в год | быстрый доступ по ключу, TTL, временные блокировки | TTL, быстрые операции по ключу | реляционная БД, в будущем можно key-value store |
| Категория | 10–15 записей, меняется редко | часто читается, редко изменяется | простое справочное хранение | реляционная БД |
| Объявление | 12 000–36 000 записей в год | лента, карточка, фильтры, статусы, закрытие, анализ состава базы объявлений | сильная консистентность, индексы, транзакции | реляционная БД |
| Фото | 60–180 тыс. записей в год | запись о фото в приложении, связь с объявлением по photoId и adId | целостность метаданных, связь с объявлением | реляционная БД |
| Бинарные файлы фото | 60–180 тыс. объектов в год | загрузка файла и последующая выдача | хранение бинарных файлов, масштабируемость, дешёвое хранение | Object Storage, например S3-совместимое хранилище |
| Отклик | 12–72 тыс. записей в год | создаётся при отклике, читается вместе с диалогом и карточкой | транзакционность и связь с объявлением | реляционная БД |
| Диалог | 12–72 тыс. записей в год | создаётся по отклику, читается по участникам | транзакции, связи между сущностями | реляционная БД |
| Сообщение | 120 – 500 тыс. записей в год | частые вставки и чтение истории диалога | быстрые вставки, согласованность с диалогом | реляционная БД |
| Owner-check | 12–72 тыс. записей в год | короткие записи и чтение статуса | транзакции и корректная смена статусов | реляционная БД |
| Вопрос owner-check | 36–216 тыс. записей в год | создаётся и читается внутри owner-check | транзакции, связь с проверкой | реляционная БД |
| Ответ owner-check | 36–216 тыс. записей в год | создаётся и читается внутри owner-check | транзакции, связь с вопросом и проверкой | реляционная БД |
| Жалоба | 1–10 тыс. записей в год | создаётся пользователем, читается и обрабатывается модератором | транзакции, корректный workflow обработки | реляционная БД |
| Результат сопоставления | 100 тыс. – 500 тыс. записей в год | пересчитывается системой, читается в карточке и блоке совпадений | допустима пересборка, важна быстрая выдача | реляционная БД |
| Событие аналитики | 1–3 млн записей в год | append-only, воронки, KPI, продуктовые метрики | накопление, агрегирование, дешёвое хранение больших объёмов | аналитическое хранилище / DWH |
| Аудит / журнал безопасности | 100–500 тыс. записей в год | append-only, разбор спорных действий и security-событий | история, надёжность, поиск по журналу | запись в реляционной БД + выгрузка в аналитическое или логовое хранилище |
4.3. Вывод по выбору
Для MVP почти все основные данные лучше хранить в реляционной БД, потому что они связаны между собой и требуют целостности. Исключения это бинарные файлы фотографий, и аналитика: для них нужны отдельные специализированные хранилища. Фото в системе хранится в двух частях, метаданные фото хранятся в реляционной БД, а бинарный контент файла в object storage.
Если число объявлений и связей между ними заметно вырастет, можно добавить графовую БД для сопоставления объявлений. Она может использоваться не как основное хранилище системы, а как дополнительный слой для хранения и анализа связей вида «объявление A похоже на объявление B», «у объявлений общая география», «есть цепочка похожих совпадений».
Временные данные авторизации могут храниться в реляционной БД. Использование key-value store рассматривается как улучшение для TTL, быстрых операций по ключу и масштабирования при росте нагрузки.
5. Какие данные стоит сразу собирать для аналитики
5.1. Зачем нужна аналитика
Так как важны следующие KPI:
-
количество зарегистрированных пользователей;
-
количество активных объявлений в месяц;
-
доля объявлений со статусом «закрыто»;
-
среднее время до первого отклика;
-
среднее время создания объявления.
Поэтому события аналитики и аудит нужно закладывать сразу, чтобы после запуска можно было не только анализировать поведение пользователей, но и проверить, достигает ли продукт заявленных целей.
5.2. Метрики и события, которые их подтверждают
| Метрика | Как считается | Какие события нужны |
|---|---|---|
| Количество зарегистрированных пользователей | число новых пользователей за период | sign_up_completed |
| Количество активных объявлений в месяц | число объявлений со статусом active за период | ad_create_completed, ad_closed |
| Доля закрытых объявлений | отношение закрытых объявлений к числу созданных | ad_create_completed, ad_closed |
| Среднее время до первого отклика | разница между созданием объявления и первым откликом | ad_create_completed, response_created |
| Среднее время создания объявления | разница между началом формы и успешной публикацией | ad_create_started, ad_create_completed, ad_create_failed |
| Конверсия из просмотра в отклик | доля объявлений, по которым после просмотра создан отклик | ad_opened, response_created |
| Конверсия owner-check в подтверждение | доля owner-check со статусом approved | owner_check_started, owner_check_approved, owner_check_rejected |
| Жалобы на объявления | число жалоб и доля обработанных жалоб | report_created, report_resolved, ad_hidden_by_moderator |
| Использование карты и фильтров | частота открытия карты и применения фильтров | map_opened, map_marker_opened, filters_applied, search_submitted |
| ER-диаграммы |