LINUX.ORG.RU

Пишем мессенджер. Подумаем об архитектуре БД для черновиков.

 


0

2

Есть мессенджер, в котором несколько разных распределённых баз, ничего не знающих друг от друге и не имеющих распределённых транзакций. Одна база - это, условно, как отдельный постгрес, а распределённая - это значит пошарденая по какому-то ключу. Реально там не постгресы, а скорее рэдисы. То есть, каждая база - это несколько каких-то редисов. У баз есть кодовые имена, типа:

  • MESSAGE - хранит сами сообщения; это особая база, она хранит каждый чатик как вектор объектов Message, больше ничего не умеет. Зато умеет зверски быстро отдать любой подинтервал любого чатика или вставить мессагу в середину чатика длиной миллиард месаг за микросекунду.
  • LIKES - хранит лайки к чему-то, может вернуть число лайков под каким-то объектом по его ID, больше ничо не умеет. Ну и умеет гарантировать, что ты не лайкнул что-то 2 раза. Реакции короче.
  • ACCOUNT - хранит профили юзеров. Пароли там всякие, ID фотки аватара и что-то такое.
  • SESSION - хранит куки или какие-то залогиненные сессии; Буквально это хештаблица sid=uid.
  • ATTACH - хранит инфу про аттачи; буквально хештаблица attach_id = (кодовое filename в хранилище файликов, автор аттача, когда запощено, тип аттача, что-то ещё..)
  • ATTACH_HASH - хранит хеши известных файлов, дедупликация аттачей, чтобы 2 раза одно и тоже не хранить
  • ATTACH_UID - обратный индекс для аттачей uid=[список всех аттачей этого юзера, чтобы можно было все экстремистские материалы сразу майору слить когда террориста поймали]
  • LIBRARY - информация про чатики или группы или каналы. Длинный профиль и настройки чатика. Буквально хештаблица chat_id = (структура данных про этот чатик рассказывающая - кто создал, когда, полное имя, полное описание, аватарка, флаги разрешённых действий с чатиком, список модеров и владельцев и прочая херобаза)
  • CONTACTLIST - контактлисты юзеров. Ты в чатик зашёл, тебе его в контактлист захерачили. В мессенджер вошёл - твой контактлист вычитали и тебе вернули, чтобы ты мог в свои чатики или в свои контакты смотреть.
  • и т.п. Разных баз около 20 под каждый пук.

И тут мы захотели сделать черновики. Ну короче, запилили базу DRAFT и запилили функционал в клиента и в сервер. Набираемое в поле ввода периодически отправляется серверу, сервер это кладёт в отдельную базу «DRAFT». Когда клиент умер и переподключился, то открыв данный чатик ему подгрузят из DRAFT что он там набирал и он может продолжить. Всё как-бы хорошо, но есть приколы.

  1. Раньше постинг мессаги предполагал на backend 1 запрос в базу MESSAGE - запостил и готово, а теперь 2 запроса - надо ещё удалить эту мессагу из DRAFT.
  2. Если DRAFT упала и там что-то было, а ты уже запостил, то потом внезапно клиенту восстанет из ада старый черновик. Возможно это чем-то хорошо, ну мол лучше данные сохранить, чем потерять, но всё равно странновато.

Потом возникла идея, которую надо обсудить.

  1. Выкинуть базу DRAFT и сохранять черновик в базу MESSAGE в отдельную табличку в той же шарде. Когда юзер постит, сделать хитрее: выполнить в базе особо хитровыдуманную атомарную команду «move» которая атомарно-транзакционно переместит черновик как он есть в таблицу сообщений (тут предполагается, конечно, что мы доводим черновик перед этим до состояния финальной мессаги, то есть досылаем в черновик те изменения, которые юзверь сделал после последнего SAVE_DRAFT и до MSG_SEND). В постгресе такой «move» бы наверно делался как небольшая транзакция вида "BEGIN; вставить в MSG результат подзапроса из DRAFT; delete from DRAFT; COMMIT;). Решается сразу проблемы (1) и (2).

Но тут возникла такая приколюха:

  1. Если раньше база MESSAGE была нагружена на запись только тем, что туда постили финальные мессаги, то теперь каждые 2 секунды туда будет апдейт черновика от каждого кто печатает. То есть, это сразу кратный рост нагрузки в разы. Прям в разы. Потому что для каждого «поста» финального сообщения в чатик, который совершался и раньше, теперь этот пост ещё льётся нам каждые 2 секунды в эту базу в отдельную табличку апдейтом. Теперь если посмотреть на пишущую нагрузку в базу MESSAGE то добавление финальных сообщений в неё - это теперь не основная нагрузка, основная - куча апдейтов черновиков.

Короче как лучше не понятно. Ужирнить MESSAGE и забить болт или всё таки юзать схему с отдельной базуней DRAFT?



Последнее исправление: lesopilorama (всего исправлений: 1)
Ответ на: комментарий от MKuznetsov

отличается что набираемый текст будет передаваться сразу, без нажатия пользователем enter/send/save (это spy-ware) и не будет истории на серверах. И тем что эта функция побочная, она не нужна в основном функционале.

На Телеграме так есть. Отправить не нажимаю, у собеседника появляется «Печатается», после этого открываю с сотового и вижу этот текст в поле ввода (неотправленный).

monk ★★★★★
()
Ответ на: комментарий от monk

На Телеграме так есть. Отправить не нажимаю

вот отчасти потому его текущие проблемы :-)

микс текстового и видео-чата. Нельзя транслировать и сохранять текст как видео, в процессе набора

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

Нельзя транслировать и сохранять текст как видео, в процессе набора

Тогда почему mail.ru не забанен? И Яндекс.Документы?

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)

Я думаю, правильное решение это сохзранять ченовик на переходе в другую вью, или в случае веба когда окно становится неактивным, минимум нагрузки на сервера и это наврено тот самый случай, когда пользователь хочет «сохранить» черновик.

masa ★★★
()
Последнее исправление: masa (всего исправлений: 1)
Ответ на: комментарий от masa

Сразу после окончания активного набора (задержка больше средней между вводом знаков) - это более хороший подход. Вдруг ракета прилетит и электричество на компьютере клиента закончится?

Saakx
()
Ответ на: комментарий от Saakx

Ну а что значит «после активного набора» очень недетерменированый параметр. 5 - 10 секунд? Вдруг это время больше таймаута суспенда стриптов/приложения в пользвательской среде. Отловить именно переход в другое приложение/вкладку вроде как можно, а вот в неактивном состоянии там неизвестно что проиходит.

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

masa ★★★
()
Ответ на: комментарий от monk

Тогда почему mail.ru не забанен? И Яндекс.Документы?

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

В отличии от просто чатиков, они документ-центрик, почти саас. Там более большой EULA (который впрочем никто не читает), «знай своего клиента».

у ТС даже в единственном аспекте DRAFT, огромные проблемы с безопасностью. Если он озвучит архитектуру/идеи полностью там очевидно просто сплошная дыра. Он когда в сеть запустит, первым ведь не тов.майор приходит - сразу начинают ломать/вертеть/и_в_нехорошее_использовать.

MKuznetsov ★★★★★
()
Ответ на: комментарий от lesopilorama

Ты майорный теоретек, в сети мы уже лет 5, причём в Россиюшке. В чём дыра ты так и не смог обьяснить, чтобы эти обьяснения выдерживали контр-аргументы.

судя по воплю - школьник што-ли ? делай как придётся...

MKuznetsov ★★★★★
()
Ответ на: комментарий от lesopilorama

Нет.

По задумке же черновик синхронизируется между устройствами, то есть у всех устройств будет один UUID черновика, пока одно из них его не отправит.

И, соответственно, когда одно из устройств отправит этот черновик, для всех устройств UUID последнего сообщения этого юзера в этом чате будет значить, что черновик надо скрыть, если он ещё не удалён. Не забываем, что в нормальных условиях черновик живёт после отправки максимум секунды (потому что бекэнд его тут же удаляет после вставки сообщения, просто из-за твоей хотелки с отсутствием транзакций это может произойти с лагом, но обязательно однажды произойдёт, даже если бекэнд упадёт между вставкой и удалением, у него должен быть какой-то механизм ретраев или фоновой подчистки мусора «удалить все черновики, для которых существуют записи в таблице сообщений с тем же UUID»).

Если же после отправки черновика какое-то устройство создаст новый черновик (с другим UUID), то этот черновик, очевидно, уже не нужно скрывать, потому что он актуальный и юзер его может редактировать на других устройствах.

Если несколько устройств создадут независимо черновики с разными UUID, то сервер может разрешить этот конфликт по принципу «кто первый встал того и тапки» (запушить этот черновик в остальные клиенты с заменой имеющихся на них черновиков для этого чата, определять кто победил можно по дате создания черновика, если юзер играется с часами и они у него не синхронизированы между устройствами, то он ССЗБ и то что у него не самый свежий черновик заменит свежий это нормально, в крайнем случае можешь встроить в протокол своего мессенджера application-level аналог NTP лол).

Если юзер написал на устройстве черновик, потом отключил его от сети, потом отправил этого черновик с другого устройства и ещё несколько сообщений после (что теперь последний UUID не этот черновик, да и в истории его не видно), а потом включил то устройство, то оно неизбежно будет подгружать кучу всего пропущенного (сообщения в чатах, черновики и т. д.), так что отображение какое-то время устаревшей информации включая черновик ожидаемо, но он исчезнет, когда подоспеет информация об удалении черновика.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 3)
Ответ на: комментарий от KivApple

Так, мне нужен ещё раз полный алгоритм. Как я поверхностно понял:

  1. При начале существования черновика, у верновика появляется рандомный ID. Сервер выдал или клиент придумал - не суть.
  2. Сервер для данного чата решает, какой ID победил, если случайно несколько черновиков решили родиться и подраться. На все клиенты сервер рассылает один и тот же черновик в таком случае.
  3. Не рассматриваем (2), считаем что клиент один или что конфликт решён.
  4. Ключевой момент: когда черновик ID был запощен в чатик, это и есть транзакция удаления черновика. Черновик таким ID, которое было запощено в чат - мёртв.
  5. От чата требуется вернуть для данного юзера ID черновика, который был последним из всех «преобразован» в настоящее сообщение от этого юзера в этот чат. Таким образом, чат вернёт ID = 12345 и на клиенте будет лежать ID = 12345 и клиент поймёт, что этот черновик мёртв.

Так, да?

lesopilorama
() автор топика
Ответ на: комментарий от lesopilorama

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

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

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 3)
Ответ на: комментарий от KivApple

Хорошо я понял. Но проблема в том, что у чатика, лежащего в базе, тупо нет способа «быстро найти последнее сообщение юзера uid» или «быстро вернуть некий UUID последнего отправленного сообщения юзером uuid», что какбэ равноценно почти.

lesopilorama
() автор топика