<< Click to Display Table of Contents >> Разработка > Рекомендации по разработке для масштабируемых систем > Оптимизация работы с сущностями Блокировка сущности перед изменением |
ВАЖНО. Перед изменением сущности установите на нее блокировку сразу после получения из базы данных. Не рекомендуется устанавливать блокировку надолго, после сохранения изменений ее нужно сразу снять.
Если блокировка не установлена, то на момент сохранения отредактированных данных, сущность может быть уже изменена. При изменении одной и той же сущности в разных потоках происходит рассинхронизация данных. Также версия может устареть, если карточка открыта длительное время в модальном окне. Чтобы ошибки рассинхронизации не возникали, после закрытия модального окна состояние сущности обновляется в базе данных, перезагружаются права доступа, несохраненные изменения теряются.
Чтобы избежать потери данных в случае отсутствия блокировок, в системе Directum RX используется версионирование сущностей:
1.Процесс 1 получает сущность из базы данных и вносит локальные изменения без установки блокировки.
2.Процесс 2 получает сущность из базы данных и вносит локальные изменения без установки блокировки.
3.Процесс 1 сравнивает изменяемую сущность с ее версией на сервере и записывает изменения в базу данных.
4.Процесс 2 сравнивает изменяемую сущность с ее версией на сервере. Если есть различия, то изменения не записываются в базу данных.
В результате, если действие выполнялось на веб-сервере, то в веб-клиенте возникает ошибка, и действие по изменению сущности нужно повторить:
Если изменения в базу данных вносил другой сервис, например Worker, то ошибка в веб-клиенте не отображается, а действие отправляется на переповтор.
В лог-файлах фиксируются исключения:
•Sungero.Domain.Exceptions.StaleEntityException – сущность изменена в параллельном потоке;
•Sungero.Domain.Exceptions.StaleEntityNotFoundException – сущность удалена в параллельном потоке.
Ошибки означают, что изменения были внесены одновременно разными сервисами, и в прикладном коде не хватает блокировок сущностей. Например, если сотрудник пытается выдать права доступа, а в это время их меняет фоновый процесс.
Как определить, где не хватает блокировки
1.Включите логирование изменений версии для всех сущностей. Для этого в конфигурационный файл config.yml в секцию common_config добавьте параметр ENTITY_VERSIONING_LOG_ENABLED и укажите в нем значение true.
При возникновении конфликтов в лог-файле фиксируются исключения StaleEntityException или StaleEntityNotFoundException.
Исключение StaleEntityException содержит информацию:
•ActualVersion – актуальный номер версии в базе данных;
•StaleVersion – локальный устаревший номер версии;
•InternalStaleVersion – внутренний устаревший номер версии. Обычно номер совпадает с указанным в StaleVersion.
Для StaleEntityNotFoundException фиксируется:
•EntityName – имя типа сущности.
•Id – идентификатор сущности.
Для типа логгера VersioningLogger в атрибуте cust записывается дополнительная информация, если сущность была изменена:
•Type – тип сущности;
•Id – идентификатор сущности;
•Version – новый актуальный номер сущности;
•OldVersion – предыдущий номер сущности.
Если сущность была удалена, то для типа логгера VersioningLogger фиксируется только номер удаляемой версии.
2.Когда ошибка возникнет, найдите ее в лог-файлах по типу исключения StaleEntityException или StaleEntityNotFoundException.
Предположим, ошибка зафиксирована в лог-файле веб-сервера:
{
"t":"2023-08-21 11:35:39.442+04:00",
"pid":"18172+111",
"tr":"cl-d5f2f4b5-669e2e",
"l":"Error",
"lg":"ExceptionPolicy",
"mt":"Запись \"Sungero.VT.Server.Data\" с идентификатором 1 была изменена в другой транзакции.",
"ex":{
"type":"Sungero.Domain.Exceptions.StaleEntityException",
"m":"Запись \"Sungero.VT.Server.Data\" с идентификатором 1 была изменена в другой транзакции.",
"stack": //"---> NHibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Sungero.VT.Server.Data#1]
//...
"cust":{
"EntityName":"Sungero.VT.Server.Data",
"Id":1,
"ActualVersion":11,
"StaleVersion":10,
"InternalStaleVersion":10
},
"isInternal":false
},
"un":"Administrator",
"tn":"Directum RX",
"v":"4.9.0.0000"
}
3.По времени возникновения ошибки найдите в лог-файлах других сервисов, где произошло параллельное изменение сущности.
Предположим, в лог-файле сервиса асинхронных событий есть запись от логгера VersioningLogger с сообщением «Entity was updated» примерно в то же время. По дополнительной информации из сообщения можно определить, что это та сущность, с которой возникла проблема:
{
"t":"2023-08-21 11:35:38.854+04:00",
"pid":"23040+79",
"tr":"cl-d5f2f4b5-138ae617df",
"l":"Debug",
"lg":"VersioningLogger",
"mt":"Entity was updated",
"cust":{
"Type":"Sungero.VT.Server.Data",
"Id":1,
"Version":11,
"OldVersion":10
},
"un":"Service User",
"tn":"Directum RX",
"v":"4.9.0.0000"
}
4.В лог-файле сервиса асинхронных событий по идентификатору трассы определите асинхронный обработчик или фоновый процесс. В примере по идентификатору трассы cl-4ebda1cc-739a4fbc07 находится асинхронный обработчик TestAsyncHandler:
{
"t":"2023-08-21 11:35:38.670+04:00",
"pid":"23040+283",
"tr":"cl-4ebda1cc-739a4fbc07",
"l":"Info",
"lg":"AsyncHandlerOperation",
"span":{
"status":"Started",
"name":"execute",
"asyncHandler":"TestAsyncHandler"
},
"un":"Service User",
"tn":"Directum RX",
"v":"4.9.0.0000"
}
5.Проанализируйте код действия, в котором возникает ошибка, и код асинхронного обработчика, где произошло параллельное изменение.
Пример кода выполнения действия:
public virtual void TestFunc()
{
// Получить сущность для изменения без установки блокировки.
var data = Functions.Data.Remote.GetDatas().ShowSelect();
if (data == null)
return;
// Запустить АО, который будет менять ту же сущность.
var asyncHandler = AsyncHandlers.AsyncHandler.Create();
asyncHandler.Id = data.Id;
asyncHandler.ExecuteAsync();
// Показать диалог. В это время АО может успеть выполниться и изменить сущность.
var dialog = Dialogs.CreateConfirmDialog("?");
if (!dialog.Show())
return;
data.Name = Guid.NewGuid().ToString();
// Попытаться сохранить сущность, но она уже устарела. Возникает ошибка.
data.Save();
}
Пример кода асинхронного обработчика:
public virtual void AsyncHandler(Sungero.VT.Server.AsyncHandlerInvokeArgs.AsyncHandlerInvokeArgs args)
{
// Получить сущность для изменения без установки блокировки.
var data = Datas.GetAll(x => x.Id == args.Id).First();
data.Name = Guid.NewGuid().ToString();
data.Save();
}
6.Добавьте блокировку в код действия и код асинхронного обработчика сразу после получения сущности. После сохранения изменений блокировку нужно снять.
В результате при выполнении действия устанавливается блокировка на сущность, и другие процессы не могут ее изменить. Действие успешно изменяет сущность, в это время асинхронный обработчик уходит на переповтор. Он отработает после сохранения сущности в базе данных. При этом в лог-файле фиксируются исключения RepeatedLockException, связанные с блокировками.
© Компания Directum, 2024 |