БД на мессенджера (ч.1): проектираме конструкцията на базата

Как да преведем бизнес изисквания в конкретни структури от данни на примера на проектирането на база за месинджър "от нула".

БД на мессенджера (ч.1): проектираме конструкцията на базата
Нашата база няма да бъде толкова мащабна и разпределена, както при ВКонтакте или Badoo, а "за да има", но да е добре — функционално, бързо и да се събира на един сървър PostgreSQL — за да може да разположим отделен екземпляр на услугата някъде отстрани, например.

Затова няма да засягаме въпросите за шардирование, репликация и георазпределени системи, а ще се фокусираме върху схемните решения вътре в БД.

Стъпка 1: Малко бизнес-специфика

Нашето обменяне на съобщения ще проектираме не абстрактно, а ще го вградим в средата на корпоративната социална мрежа. Тоест, хората при нас не "просто си разменят съобщения", а общуват помежду си в контекста на решаването на определени бизнес задачи.

А какви бизнес задачи има?.. Нека видим на примера на Василий — ръководителя на отдела по разработка.

  • "Николай, ние имаме нужда от пача за тази задача днес!"
    Значи, кореспонденцията може да се води в контекста на някакъв документ.
  • "Коля, да играем ли Dota вечерта?"
    Тоест дори и при само една двойка събеседници общуването може да се води по различни теми.
  • "Петър, Николай, вижте приложението с цените за новия сървър."
    Така, едно съобщение може да има няколко получатели. При това, съобщението може да съдържа прикачени файлове.
  • "Семен, и ти също хвърли поглед."
    И трябва да има възможност в съществуващата кореспонденция да поканим нов участник.

За сега да спрем на този списък с "очевидни" нужди.

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

Стъпка 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)
);

Тук приложихме два типични подхода, използвани при създаване на помощни таблици:

  • Умножаване на записите
    Създаваме по една оригинална запис на съобщение и веднага няколко производни записи в различни видове регистри за различни собственици — както за изпращача, така и за получателя. Затова всеки от регистрите сега попада на индекс — понеже в типичния случай искаме да видим само първата страница.
  • Уникализиране на записите
    При всяко изпращане на съобщение в рамките на конкретна тема е достатъчно да проверим дали вече съществува такъв запис. Ако не — добавяме го в нашия «речник».

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

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster