Как синхронизировать действия пользователей и данные в многопользовательской системе?
Доброго времени суток!
В процессе разработки многопользовательской системы (фронтенд + микросервисный бэкенд) столкнулся с непониманием как правильно обеспечить одновременную работу пользователей.
Суть проблемы:
- пользователь user1 зашел на страничку редактирования информации, выбрал пункт info1 и начинает его редактировать,
- в это же время пользователь user2 тоже выбрал info1, сделал небольшую правку и нажал Сохранить.
- однако user1 ничего не знает о том что данные были изменены другим пользователем, вносит свои правки и также нажимает кнопку Сохранить.
Ситуация еще несколько осложняется тем, что для полного сохранения данных иногда нужно некоторое время, т.к. они разбросаны между микросервисами расположенными на разных серверах.
Т.е. после того как пользователь user2 нажал Сохранить данные, то он получил уведомление что данные будут изменены, создается задача на изменение данных, меняется номер версии данных и начинается их сохранение-синхронизация.
Как правильно мне поступить в этой ситуации относительно пользователя user1?
Мои мысли:
1 - уведомить пользователя user1 о том что данные были изменены и попросить дождаться синхронизации данных, после этого по-новому подтянуть актуальные данные и повторить потом операцию редактирования.
2 - просто создать задачу на изменение данных от user1 и поместить ее в очередь выполнения. В результате перезапишуться данные, введенные пользователем user2. При этом пользователь user1 не будет знать что он редактировал неактуальные данные, это вроде как нехорошо.
- возможно есть другие варианты...
И посоветуйте, пожалуйста, что можно почитать по алгоритмам создания подобных систем где присутствуют следующие нюансы:
- больше одного пользователя, имеющего одновременный доступ к системе,
- общие данные, которые могут меняться любым из пользователей,
- история операций чтобы можно было узнать кто изменил те или иные данные и когда.
Есть ли какое-то более обобщенное название у подобных систем?
P.S. Заранее прошу прощения, тэги вопроса мог поставить некорректно, т.к. не смог сходу придумать какие правильно подобрать.
Вы описываете типичную ситуацию при работе с базами данных.
Обычно применяются 2 стратегии:
1. ничего не делаем, побеждает последний записавший данные. Запись, конечно, должна быть атомарной, т.е. если 2 пользователя одновременно пишут, то в итоге должны быть записанны данные либо первого пользователя либо второго, но не нечто среднее.
2. блокировка доступа на изменение, в этом случае описанная ситуация просто не возникнет.
Эти же подходы вполне применимы и в вашем случае.
Оба подхода имеют свои достоинства и недостатки, нужно оценить вашу конкретную ситуацию и выбрать более подходящий подход.
Adamos, спасибо! Я думал по поводу хранения обеих версий. Пока останавливает то, что я в данный момент еще пока не сильно могу понять какое кол-во данных у меня будет, а хранение всех версий автоматически увеличит это кол-во данных.
res2001, спасибо! По всей видимости для начала реализую некую блокировку данных. А потом буду смотреть в сторону хранения версий, чтобы лучше понимать кто что менял.
Павел, вопрос в том, что у вас за система вообще. Может оказаться, что вам вообще актуально хранение по принципу Википедии (кто угодно может переписать, но хранятся все версии, и отображается только проверенная).
А может - сохранение обеих версий и назначение ответственного за мердж.
В любом случае вопрос в первую очередь не об объемах, а о нужности информации и возможностях, которые открывает ее хранение.
Adamos, это система контроля доступа, однако кроме функционала обычного СКУД (карточка, расписание, события) она содержит еще и различную информацию о пользователях, их должностях, отслеживает смены этих должностей и соответственно вносятся изменения в права доступа.
Информация у меня не совсем текстовая, потому мерджи у меня не особо применимы.
Adamos, я не стараюсь высосать из пальца проблему. Мне просто хочется понять как принято правильно обрабатывать подобные ситуации чтобы и для пользователя оно было удобно потом и в системе не возникало каких-либо проблем (к сожалению, пока не хватает опыта чтобы наперед чувствовать всякие "подводные камни").
Павел, правильно - не обобщать, а рассматривать конкретные кейсы. Если самое простое, топорное решение закрывает кейс и не грозит вырасти в проблему в обозримом будущем - ничего более сложного и не выдумывать. Разве что те сложности позволят развить какие-то новые направления.
Павел, По моему вам лучше перед сохранением данных сделать проверку - не изменились ли данные, и только если не изменились - сохранять. Если изменились - возврат пользователю с ошибкой "данные изменены".
У вас должна быть реализована операция сохранения типа CAS из многопоточного lock-free программирования. Т.е. проверка "не изменились ли данные" и если не изменились, то сохранить, должна быть реализована атомарно, иначе возможна ситуация, когда между проверкой и сохранением данные будут изменены другим пользователем.
Можно блокировать редактирование данных пока user1 эти данные редактирует, т.е. выводить оповещение для user2 что в данный момент данные редактируются и предложить продолжить редактирование несмотря ни на что.
1 - уведомить пользователя user1 о том что данные были изменены и попросить дождаться синхронизации данных, после этого по-новому подтянуть актуальные данные и повторить потом операцию редактирования.
Макс, спасибо! Я пока не сильно понимаю для себя как реализовать подобную блокировку. Я изначально не уточнил все моменты - у меня frontend на angular и с бэкендом общается по REST.
В таком случае наверное проблематично будет блокировать данные, потому что да я знаю что пользователь запросил данные, но страничка у него может быть открыта бесконечно долго и ничего на ней не происходить. Я не могу держать данные блокированными все это время для других пользователей.
Ну хотя если другим пользователям выводить уведомление и разрешить принудительное редактирование..то возможно.
Я думал блокировать данные от момента когда user2 нажал сохранить и данные поступили на сервер, до момента когда данные полностью "разойдутся" по системе и установиться флаг что данные в порядке.
При этом я разве что могу пользователю user1 вывести сообщение когда тот также нажмет сохранить и я увижу на сервере что пришли новые данные а старые еще "не в порядке".
Ну либо периодически перезагружать актуальные данные с сервера увеличивая нагрузку на него.
В таком случае наверное проблематично будет блокировать данные, потому что да я знаю что пользователь запросил данные, но страничка у него может быть открыта бесконечно долго и ничего на ней не происходить. Я не могу держать данные блокированными все это время для других пользователей.
С фронта можно отправлять запрос, например, каждую минуту если user1 за эту минуту что-то делал (печатал, двигал мышью и т.п.), в базе для сущности редактируемых данных добавить дату последнего запроса пришедшего с фронта editing_date, а когда user2 захочет изменить данные, проверять сначала editing_date и если с момента этой даты не прошло, допустим, 2 минуты то это значит что данные редактируются и нужно блокировать редактирование для user2. После того как user1 отредактировал данные editing_date можно обнулить, или еще как-нибудь определить что данные отредактированы, например, по дате последнего обновления данных, если эта дата есть в базе, типа editing_date < update_date и т.п.
Ещё есть более красивый, но на 2 порядка более сложный вариант — CRDT https://m.habr.com/ru/post/418897/, привожу его здесь скорее для полноты ответов, чем как реальный совет, никому не рекомендую использовать CRDT в проде без необходимости.
Вероятно одним из решений будет внедрение возможности совместного редактирования документа. Посмотрите в сторону collaborative editors. Например, ProseMirror. Не могу сказать наверняка, что решение хорошее, но как вариант, думаю, рассмотреть стоит.