Как использовать уровень изоляции Serialization с большими запросами?
Есть бекенд который работает с финансами, я установил уровень изоляции Serialization для всех транзакций (PostgreSQL DB). Транзакции открывается в начале запроса и закрывается в самом конце. Проблема заключается в запросах с большим количеством затрагиваемых сущностей, то есть, например у нас есть запрос на создание ордера, в этом запросе происходит чтение и запись в несколько таблиц, что по логике вещей, увеличивает вероятность получения ошибки сериализации.
Как будет правильно обробатывать транзакции которые читаю и вносят изменения в несколько таблиц?
Возможно уже есть какие-то устаканившиеся решения для это проблемы?
Everything_is_bad, до этого была дефолдная с асинхронными блокеровками, но я понимаю то что, чем больше система растет тем меньше я могу просчетать все случаи и все уязвимые места обвернуть в блокировки.
Если нужно пример блокировки: Есть 2 ендпоинта, добавить ордер и редактировать ордер, в начале каждого из ендпоинтов открывается локер по ключу order_id и значению айди ордера, это сделано для того чтобы нельзя было изменять один и тот же ордер одновременно. Но опять также, внутри самого изменения ордера еще изменяется несколько таблиц и тут нужно или все колосально в локеры обоворачивать или тестировать сутками на пролет.
Есть 2 ендпоинта, добавить ордер и редактировать ордер
Можно при редактировании ордера отменять изначальный и создавать новый с новыми параметрами, например.
Или делать event sourcing и работать с сущностями - как с цепочками событий, которые происходят с этой сущностью.
Только под одной сущности будет две записи: создание, редактирование. Для ускорения чтения можно сделать отдельно агрегат, где они будут схлопываться в одну запись.
Но опять также, внутри самого изменения ордера еще изменяется несколько таблиц и тут нужно или все колосально в локеры обоворачивать или тестировать сутками на пролет.
Можешь чуть подробнее, как задеваются другие таблицы? Без этого сложно дать полезный совет.
"увеличивает вероятность получения ошибки сериализации". Так особенность работы изоляции Serialization в том и есть, что если кто-то вне транзакции даже прочитал тоже что и вы, то сохранение не пройдет.
Поэтому нужно предусматривать возможность несколько раз повторить попытку сохранения.
Готовьте заранее все данные, что нужны для сохрания в read commited изоляции. И только для быстрого изменения/создания запускайте транзакцию в Serialization и как можно скорее ее закрывайте.
Можешь чуть подробнее, как задеваются другие таблицы? Без этого сложно дать полезный совет.
Например, есть ендпоинт для редактирования p2p ордера и человек хочет завершить ордер тем самым перевести внутри платформы деньги, так вот, происходит не только изменение ордера но и изменение кошелька + изменения например дневного объема пользователя + создание транзакции.
И только для быстрого изменения/создания запускайте транзакцию в Serialization и как можно скорее ее закрывайте.
Так и буду делать, но открытие Serialization транзакции на короткий промежуток времени это только временная мера, так как от роста пользователей я буду возвращаться к этой проблеме снова.
Готовьте заранее все данные, что нужны для сохрания в read commited изоляции.
Запросы к базе данных происходят в разных классах, я не могу достать их просто, это ломает структуру.
Например, есть ендпоинт для редактирования p2p ордера и человек хочет завершить ордер тем самым перевести внутри платформы деньги,
Так может это тогда не редактирование, а как раз закрытие/завершение ордера?
так вот, происходит не только изменение ордера но и изменение кошелька + изменения например дневного объема пользователя + создание транзакции.
Во всяких банковских системах тут используются события и идёт так:
1. Создаётся событие/сообщение, что пользователь хочет вот это, мы сразу ему помечаем ордер как закрытый или переводим в какой-то промежуточный статус.
2. Это событие обрабатывается и обновляются всякие лимиты/дневные объёмы, обновляется баланс. (это могут делать разные системы)
3. Транзакция фиксируется. Ордер переводится в окончательный статус. Можно ещё раз пересчитать транзакции и обновить баланс.
Коммиты происходят в разных классах, я не могу достать эти коммиты просто, это ломает структуру.
Тогда надо ещё подумать над архитектурой кода. Обычно заводят какой-нибудь UnitOfWork в котором можно обратиться к текущей транзакции или начать новую
1. Создаётся событие/сообщение, что пользователь хочет вот это, мы сразу ему помечаем ордер как закрытый или переводим в какой-то промежуточный статус.
Это похоже на микросервисную архитектуру, но в данный момент времени у меня монолит.
Тогда надо ещё подумать над архитектурой кода.
Я ошибся, я имелл виду запросы, коммит у меня только один и в конце ендпоинта.
Василий Банников, их можно делать везде, но я их делаю только в конце для целесообразности. Я думал о частичных коммитах, но мне не нравится тот сценарий например, что часть выполниться а часть нет.
Maxwell012, ну в том же постгресе есть чекпоинты - ты в случае ошибки можешь откатиться к чекпоинту, а можешь полностью отменить транзакцию. Так ты сможешь временно повышать уровень изоляции.
Использовать очередь конкурирующих запросов.
У меня, например, часто конфликтовали запросы на изменение поля `Project.LastModificationDate` когда несколько пользователей одновременно сохраняют его. Я создал очередь по `ProjectId` для всех сохранений. И таких видов очередей у меня несколько.