ಮೆಸೆಂಜರ್ ಡೇಟಾಬೇಸ್ (ಭಾಗ 1): ಮೂಲ ಚೌಕಟ್ಟನ್ನು ವಿನ್ಯಾಸಗೊಳಿಸುವುದು

Как можно перевести бизнес-требования в конкретные структуры данных на примере проектирования «с нуля» базы для мессенджера.

ಮೆಸೆಂಜರ್ ಡೇಟಾಬೇಸ್ (ಭಾಗ 1): ಮೂಲ ಚೌಕಟ್ಟನ್ನು ವಿನ್ಯಾಸಗೊಳಿಸುವುದು
Наша база будет не такой масштабной и распределенной, как у ВКонтакте ಅಥವಾ Badoo, а «чтобы было», но было хорошо — функционально, быстро и умещалось на одном сервере PostgreSQL — чтобы можно было развернуть отдельный экземпляр сервиса где-то на стороне, например.

Поэтому не будем затрагивать вопросы шардинга, репликации и геораспределенных систем, а сосредоточимся на схемных решениях внутри БД.

Шаг 1: Немного бизнес-специфики

Наш обмен сообщениями мы будем проектировать не абстрактно, а встраивать в окружение корпоративной соцсети. То есть люди у нас не «просто переписываются», а общаются между собой в контексте решения определенных бизнес-задач.

А какие бывают задачи у бизнеса?.. Посмотрим на примере Василия — руководителя отдела разработки.

  • «Николай, вот по этой задаче патч нужен уже сегодня!»
    Значит, переписка может вестись в контексте какого-то ಡಾಕ್ಯುಮೆಂಟ್.
  • «Коля, го вечером в доту?»
    То есть даже у одной пары собеседников общение одновременно может вестись по разным темам.
  • «Петр, Николай, посмотрите в аттаче прайс на новый сервер.»
    Так, у одного сообщения может быть несколько адресатов. При этом сообщение может содержать прикрепленные файлы.
  • «Семен, и ты тоже взгляни.»
    И должна быть возможность в уже существующую переписку пригласить нового участника.

Остановимся пока на этом перечне «очевидных» потребностей.

Без понимания прикладной специфики задачи и задаваемых ей ограничений, спроектировать эффективную схему БД для ее решения практически невозможно.

Шаг 2: Минимальная логическая схема

Схемно пока все получается очень похоже на email-переписку — традиционный инструмент ведения бизнеса. Таки да, «алгоритмически» многие задачи бизнеса похожи друг на друга, поэтому и инструменты для их решения будут структурно сходны.

Давайте зафиксируем уже получившуюся логическую схему отношений сущностей. Для простоты понимания нашей модели воспользуемся самым примитивным вариантом отображения ER-модели без усложнений UML или IDEF-нотаций:

ಮೆಸೆಂಜರ್ ಡೇಟಾಬೇಸ್ (ಭಾಗ 1): ಮೂಲ ಚೌಕಟ್ಟನ್ನು ವಿನ್ಯಾಸಗೊಳಿಸುವುದು

В нашем примере персона, документ и бинарное «тело» файла — это «внешние» сущности, которые самостоятельно существуют и без нашего сервиса. Поэтому просто будем воспринимать их в дальнейшем как некоторые ссылки «куда-то» по UUID.

Рисуйте схемы как можно проще — большинство тех, кому вы их будете показывать, не являются экспертами в чтении UML/IDEF. Но — рисуйте обязательно.

Шаг 3: Набрасываем структуру таблиц

Про имена таблиц и полейಕ್ಷೇತ್ರಗಳು ಮತ್ತು ಕೋಷ್ಟಕಗಳ "ರಷ್ಯನ್" ಹೆಸರುಗಳನ್ನು ವಿಭಿನ್ನವಾಗಿ ಪರಿಗಣಿಸಬಹುದು, ಆದರೆ ಇದು ರುಚಿಯ ವಿಷಯವಾಗಿದೆ. ಏಕೆಂದರೆ ದಿ ಇಲ್ಲಿ ಟೆನ್ಸರ್ ನಲ್ಲಿ ಯಾವುದೇ ವಿದೇಶಿ ಡೆವಲಪರ್‌ಗಳಿಲ್ಲ, ಮತ್ತು PostgreSQL ಅವರು ಚಿತ್ರಲಿಪಿಗಳಲ್ಲಿಯೂ ಸಹ ಹೆಸರುಗಳನ್ನು ನೀಡಲು ಅನುಮತಿಸುತ್ತದೆ ಉಲ್ಲೇಖಗಳಲ್ಲಿ ಲಗತ್ತಿಸಲಾಗಿದೆ, ನಂತರ ನಾವು ವಸ್ತುಗಳನ್ನು ನಿಸ್ಸಂದಿಗ್ಧವಾಗಿ ಮತ್ತು ಸ್ಪಷ್ಟವಾಗಿ ಹೆಸರಿಸಲು ಬಯಸುತ್ತೇವೆ ಇದರಿಂದ ಯಾವುದೇ ವ್ಯತ್ಯಾಸಗಳಿಲ್ಲ.
Поскольку сообщения у нас пишут много людей сразу, часть из них вообще могут делать это в оффлайн-режиме, то самый простой вариант — использовать UUID в качестве идентификаторов не только для внешних сущностей, но и для всех объектов внутри нашего сервиса. Причем генерировать их можно даже на клиентской стороне — это поможет нам поддержать отправку сообщений при кратковременной недоступности БД, а вероятность коллизии крайне мала.

Черновая структура таблиц в нашей базе примет вот такой вид:
Таблицы : RU

CREATE TABLE "Тема"(
  "Тема"
    uuid
      PRIMARY KEY
, "Документ"
    uuid
, "Название"
    text
);

CREATE TABLE "Сообщение"(
  "Сообщение"
    uuid
      PRIMARY KEY
, "Тема"
    uuid
, "Автор"
    uuid
, "ДатаВремя"
    timestamp
, "Текст"
    text
);

CREATE TABLE "Адресат"(
  "Сообщение"
    uuid
, "Персона"
    uuid
, PRIMARY KEY("Сообщение", "Персона")
);

CREATE TABLE "Файл"(
  "Файл"
    uuid
      PRIMARY KEY
, "Сообщение"
    uuid
, "BLOB"
    uuid
, "Имя"
    text
);

Таблицы : EN

CREATE TABLE theme(
  theme
    uuid
      PRIMARY KEY
, document
    uuid
, title
    text
);

CREATE TABLE message(
  message
    uuid
      PRIMARY KEY
, theme
    uuid
, author
    uuid
, dt
    timestamp
, body
    text
);

CREATE TABLE message_addressee(
  message
    uuid
, person
    uuid
, PRIMARY KEY(message, person)
);

CREATE TABLE message_file(
  file
    uuid
      PRIMARY KEY
, message
    uuid
, content
    uuid
, filename
    text
);

Самое простое при описании формата — начинать «раскручивать» граф связей от таблиц, которые не ссылаются сами ни на кого.

Шаг 4: Выясняем неочевидные потребности

Все, мы спроектировали базу, в которую можно отлично писать и ಹೇಗಾದರೂ читать.

Давайте поставим себя на место пользователя нашего сервиса — что мы захотим делать с его помощью?

  • ಕೊನೆಯ ಸಂದೇಶಗಳು
    хронологически отсортированный по различным признакам реестр «моих» сообщений. Где я один из адресатов, где я автор, где мне написали, а я не ответил, где не ответили мне, …
  • Участники переписки
    Кто вообще участвует в этом длинном-длинном чате?

Наша структура позволяет решить обе эти задачи «вообще», но быстро — нет. Проблема в том, что для сортировки в рамках первой задачи невозможно создать индекс, подходящий для каждого из участников (и придется извлекать все записи), а для решения второй необходимо извлекать все-все сообщения по теме.

Непредусмотренные пользовательские задачи могут поставить жирный крест на производительности.

Шаг 5: Разумная денормализация

Обе наши проблемы помогут решить дополнительные таблицы, в которые мы будем дублировать часть данных, необходимых для формирования на них подходящих к нашим задачам индексов.
ಮೆಸೆಂಜರ್ ಡೇಟಾಬೇಸ್ (ಭಾಗ 1): ಮೂಲ ಚೌಕಟ್ಟನ್ನು ವಿನ್ಯಾಸಗೊಳಿಸುವುದು

Таблицы : RU

CREATE TABLE "РеестрСообщений"(
  "Владелец"
    uuid
, "ТипРеестра"
    smallint
, "ДатаВремя"
    timestamp
, "Сообщение"
    uuid
, PRIMARY KEY("Владелец", "ТипРеестра", "Сообщение")
);
CREATE INDEX ON "РеестрСообщений"("Владелец", "ТипРеестра", "ДатаВремя" DESC);

CREATE TABLE "УчастникТемы"(
  "Тема"
    uuid
, "Персона"
    uuid
, PRIMARY KEY("Тема", "Персона")
);

Таблицы : EN

CREATE TABLE message_registry(
  owner
    uuid
, registry
    smallint
, dt
    timestamp
, message
    uuid
, PRIMARY KEY(owner, registry, message)
);
CREATE INDEX ON message_registry(owner, registry, dt DESC);

CREATE TABLE theme_participant(
  theme
    uuid
, person
    uuid
, PRIMARY KEY(theme, person)
);

Здесь мы применили два типичных подхода, применяемых при создании вспомогательных таблиц:

  • Умножение записей
    Формируем по одной исходной записи сообщения сразу несколько записей-следствий в разные виды реестров для разных владельцев — как для отправителя, так и для получателя. Зато каждый из реестров теперь ложится на индекс — ведь в типовом случае мы захотим видеть только первую страницу.
  • Уникализация записей
    При каждой отправке сообщения внутри конкретной темы достаточно проверить, существует ли уже такая запись. Если нет — добавляем ее в наш «словарь».

В следующей части статьи речь пойдет про внедрение секционирования в структуру нашей базы.

ಮೂಲ: www.habr.com

ಕಾಮೆಂಟ್ ಅನ್ನು ಸೇರಿಸಿ