Перейти к основному содержимому

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

1. Определение сущностей, которые необходимо хранить

1.1. Перечень сущностей и ключевые атрибуты

Пользователь

Что хранит: данные учётной записи и роль пользователя в системе.

АтрибутЧто это и зачем нужно
userIdвнутренний идентификатор пользователя
emailemail для входа, если используется 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
contentTypeMIME-тип файла, например 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результат действия
ipIP-адрес
userAgentuser-agent клиента
createdAtдата и время события

1.4. Взаимодействие с сущностями и характер данных

СущностьКто взаимодействуетХарактер данных
Пользовательпользователь, система, модератортранзакционные
Согласие пользователяпользователь, систематранзакционные
Сессия / токен доступа / токен сбросапользователь, систематранзакционные временные
Категориягость, пользователь, системасправочные
Объявлениегость, пользователь, система, модератортранзакционные + аналитические
Фотопользователь, система, модератортранзакционные
Откликпользователь, систематранзакционные
Диалогпользователь, система, модератор (по жалобе)транзакционные
Сообщениепользователь, систематранзакционные
Owner-checkпользователь, систематранзакционные
Вопрос owner-checkпользователь, систематранзакционные
Ответ owner-checkпользователь, систематранзакционные
Жалобапользователь, система, модератортранзакционные
Результат сопоставлениясистема, пользовательоперационные
Событие аналитикисистема, аналитик / POаналитические
Аудит / журнал безопасностисистема, модератортранзакционные + аналитические

2. Какие акторы взаимодействуют с данными

Основные акторы

  1. Гость

    • читает ленту, карту и карточки объявлений.
  2. Авторизованный пользователь

    • регистрируется и входит;

    • принимает документы;

    • создаёт объявления;

    • загружает фото;

    • откликается на объявление;

    • участвует в диалоге;

    • проходит owner-check;

    • отправляет жалобы;

    • закрывает свои объявления.

  3. Модератор

    • работает с жалобами;

    • просматривает объявление, на которое пожаловались;

    • просматривает профиль автора объявления;

    • при необходимости проверяет связанное фото;

    • при необходимости проверяет связанный диалог;

    • скрывает объявление или ограничивает аккаунт.

  4. Система

    • валидирует данные;

    • создаёт отклики, диалоги и системные записи;

    • считает совпадения;

    • связывает фото с объявлениями;

    • фиксирует аналитические события и аудит.

  5. Аналитик / 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-check12–72 тыс. записей в годкороткие записи и чтение статусатранзакции и корректная смена статусовреляционная БД
Вопрос owner-check36–216 тыс. записей в годсоздаётся и читается внутри owner-checkтранзакции, связь с проверкойреляционная БД
Ответ owner-check36–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 со статусом approvedowner_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-диаграммы