Блокировка чтения строки в PostgreSQL - PullRequest
0 голосов
/ 20 сентября 2018

Допустим, у меня есть таблица с названием Снятие (id, сумма, user_id, статус).

Всякий раз, когда я инициирую вывод средств, это поток:

  • Проверьте, если пользовательимеет достаточный баланс (который рассчитывается как сумма полученной суммы - сумма снятия средств)
  • Вставить строку с количеством, user_id и status = 'pending'
  • Вызвать стороннее программное обеспечение через gRPC, чтобы инициироватьвывод средств (фактически отправка денег), дождитесь ответа
  • Обновите строку со статусом = 'завершено', как только мы получим положительный ответ, или удалите запись, если вывод средств не удался.

Тем не менее, у меня есть проблема параллелизма в этом потоке.Допустим, пользователь делает 2 запроса на полное снятие баланса в течение ~ 50 мс разницы:

Запрос 1

  • У пользователя достаточно баланса
  • Создать вывод (баланс = 0)
  • Обновление статуса снятия

Запрос 2 (после ~ 50 мс)

  • Пользователь имеет достаточный баланс (что не соответствует действительности, другой вкладке не удалосьеще не сохранено)
  • Создать снятие (баланс = отрицательный)
  • Обновить статус вывода

В настоящее время мы используем Redis для блокировки снятия средств с определенного пользователя, если онинаходятся в пределах x мс, чтобы избежать этой ситуации, однако это не самое надежное решение.Поскольку сейчас мы разрабатываем API для бизнеса, с нашим текущим решением мы заблокируем возможные изъятия, которые могут быть запрошены одновременно.Есть ли способ заблокировать и убедиться, что последующие запросы вставки ожидают, основываясь на user_id таблицы снятия средств?

1 Ответ

0 голосов
/ 20 сентября 2018

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

Уровень postgres по умолчанию - READ COMMITTED , который позволяет каждой из этих параллельных транзакций видеть нечто похожее (состояние доступных средств)даже если они должны быть зависимыми.

Один из способов решения этой проблемы - пометить каждую из этих транзакций как последовательность "SERIALIZABLE".

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

Это должно обеспечить правильность вашего приложения за счет доступности, т. Е. В этом случае вторая транзакция не сможет изменить записи и будет отклонена, что потребует повторной попытки.Для POC-приложений или приложений с низким трафиком это обычно первый шаг, так как вы можете убедиться в его правильности прямо сейчас.


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


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


Блокировка между машинами (например, использование redis через postgres / grpc) называется распределенной блокировкой и о ней написано хорошее количество https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

...