Каков способ Rails 6 для предотвращения запуска мультимодельной логики, если она уже запущена для пользователя?

У меня есть сложная бизнес-логика, которая запускается при выполнении действия контроллера. Логика выполняет различные обновления множества различных таблиц/записей, связанных с пользователем. Однако метод модели, вызываемый контроллером, склонен выполняться несколько раз одновременно, если маршрут выполняется достаточно быстро последовательно.

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

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

Моя логика не просто обновляет одну запись, она обновляет запись пользователя, а также множество разных записей, связанных с этим пользователем, во многих таблицах. Мне кажется неправильным добавлять lock_version к каждой отдельной таблице, но, может быть, это нормально? Я думаю, что я хочу, чтобы пользователь и связанные с ним записи были заморожены во время работы этого метода, поэтому я ищу лучший способ добиться этого.

Каков способ Rails справиться с этим (надеюсь) с минимально возможной дополнительной сложностью? Если мне нужна блокировка, нужно ли добавлять столбец lock_version к каждой отдельной модели, которая обновляется в результате этой логики?


person David Gay    schedule 05.06.2021    source источник
comment
Я не думаю, что вам нужна блокировка базы данных (я не думаю, что вы можете заблокировать определенные записи, вы в конечном итоге заблокируете таблицы для всех пользователей). Я также не думаю, что a rails way это можно сделать, это будет зависеть от каждого конкретного случая, но вы не даете четкого описания своей системы. Я понимаю, что вы хотите предотвратить слишком быстрое попадание пользователя в одну и ту же конечную точку, поэтому, возможно, вам поможет что-то вроде регулирования атаки стойки github.com/rack/rack-attack#throttling   -  person arieljuod    schedule 05.06.2021
comment
Это еще одна возможность github.com/ClosureTree/with_advisory_lock   -  person Joel Blum    schedule 06.06.2021
comment
@JoelBlum Похоже, это то, что я хочу, но у меня не получилось. Я пробовал и User.with_advisory_lock("finish_activity", transaction: true) do { my_method}, и current_user.with_advisory_lock("finish_activity", transaction: true) do { my_method }. Я все еще могу открыть 5 вкладок браузера и иметь дублирующиеся завершения, если код завершается в ту же секунду. Я использую постгрес.   -  person David Gay    schedule 06.06.2021


Ответы (1)


Создайте мьютекс, однозначно ограниченный пользователем, которого вы обрабатываете, либо с Ruby или с согласованной базой данных, если вы создаете распределенное приложение.

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

Убедитесь, что любой процесс, получивший мьютекс, освобождает его после прекращения обработки.

lock_version важен, но это всего лишь блокировка базы данных. Это гарантирует, что объект не может быть сохранен, если он уже был обновлен другим процессом.

Если вы делаете атомарные коммиты, lock_version будет работать, но лучше проверить параллельную обработку прежде чем вы перейдете к стадии фиксации. Такая блокировка на уровне процесса — это то, что вы должны реализовать сами.

person drruruu    schedule 05.06.2021
comment
Спасибо, этот мьютекс звучит как то, что я хочу. Где я должен его объявить и как я могу связать его с моей моделью пользователя, чтобы каждый пользователь получил его? Ответ на stackoverflow.com/a/24567438/1196465 показывает установку права @semaphore в модели пользователя, но это не так. Кажется, что обернуть содержимое метода в @semaphore.synchronize {} достаточно, чтобы решить проблему; Я все еще получаю повторяющиеся прогоны. Я должен что-то упустить. - person David Gay; 06.06.2021