Как правильно реализовать обработку одновременных запросов к бэкенду?
Здравствуйте! Использую Node.js и npm-пакет google-spreadsheet в качестве бэкенда для бота в Telegram, который отслеживает количество продукции на складе и позволяет сделать заказ на определённый объем этой продукции. Все данные берутся из гугл таблицы. При заказе бот проверяет остаток продукции на складе и, если желаемый объем не превышает остаток, формирует заказ.
Суть проблемы в том, что когда одновременно два пользователя делают заказ, между проверкой остатка и формированием заказа для одного из пользователей, может быть сформирован второй заказ. JavaScript ведь работает асинхронно, с неблокирующим вводом/выводом, и такое регулярно случается. Скрипт проверяет доступный остаток, формируется второй заказ, а после этого формируется исходный заказ. В результате этого получается два заказа на общий объём, превышающий доступный остаток.
Подскажите пожалуйста, как быть в такой ситуации и как сделать так, чтобы не возникало такой путаницы, и при этом сохранить асинхронность и другие преимущества от использования Node.js?
Похожая ситуация получается и при работе с файлами — если прочитать файл, изменить полученные данные и сохранить их, а в перерыве между этими операциями изменить файл сторонним скриптом, то часть данных потеряется. Суть та же.
гугл таблицы не лучшая реализация БД)))
Может стоит вынести весь товар в отдельную БД поддерживающую блокировки и транзакции а гугл таблицу использовать только как средство отображения?
Это вы серезную проблему на самом деле затронули.
варианта два
- либо сделать блокировки на уровне БД, где над такой проблемой подумали бородатые дядьки и придумали разные решения (и вам нужна будет нормальная БД)
- либо поменять систему так чтобы такой проблемы вообще не было - обеспечить обработку только одного заказа в один момент времени, если у вас скрипт работает в единственном экземпляре, то вы можете приостановить обработку всех остальных заказов, если уже какой-то один обрабатывается. Например завести очередь, при поступлении заявки класть ее в эту очередь, брать и обрабатывать по одному от начала и до конца, и когда один заказ полностью завершился и сформирован, брать следующий, обрабатывать его.
городить какие-то свои блокировки поверх гугл-таблиц - гиблое дело, вы уменьшите вероятность, но не уберете ее полностью.
Robur, первый вариант кажется удобнее и проще, наверное стоит и правда начать использовать БД. Скажите пожалуйста, если взять, например, ту же MongoDB, там блокировки уже реализованы? Или нужно будет самому дописывать эту логику при работе с БД?
Никита Корнилов, вам в любом случае надо будет что-то делать самому, просто БД предоставляет вам инструменты с помощью которых этого можно реально достичь. Такого чтобы взял БД и у вас автомагически такие конфликты решаются - нет. Не знаю насчет монго, я не копался там настолько. Можно начать отсюда: https://docs.mongodb.com/manual/core/transactions/
Но в любом случае, вам надо будет разобраться в транзакциях, их типах, атомарности операций. Не зависимо от того монго вы используете или что-то другое.
Общая схема может выглядеть, например, как-то так:: вы открываете транзакцию, читаете количество доступных товаров, создаете то что нужно создать для заказа, изменяете количество доступных товаров и делаете коммит транзакции, транзакция должна быть такой чтобы в момент коммита гарантировать что прочтенное количество доступных товаров не изменилось.
Если это выполняется, ваши данные обновляются, если коммит транзакции вызвал ошибку, значит данные за это время поменялись, и вам надо запустить процесс оформления заказа по новой - прочитать значение доступных товаров из базы и так далее.
А вот как добиться этой логики - вы уже читаете документацию по БД.
На самом деле проще как раз второй вариант ;) Очередь делается из обычного массива в JS в 15 строчек и вы забываете про проблемы одновременной записи.