Как избежать двух одновременных запросов API, нарушающих логику проверки документов? - PullRequest
0 голосов
/ 11 апреля 2019

У меня есть API, который для вставки нового элемента должен быть проверен.Проверка в основном является валидатором типа (string, number, Date и т. Д.) И запрашивает базу данных, которая проверяет, есть ли у «пользователя» «элемент» в ту же дату, что, если он выполняет проверку,неудачно.

Псевдокод выглядит следующим образом:

const Item = require("./models/item");
function post(newDoc){

  let errors = await checkForDocErrors(newDoc)
  if (errors) {
    throw errors;
  }

  let itemCreated = await Item.create(newDoc);

  return itemCreated;


}

Моя проблема в том, что если я сделаю два параллельных запроса, как это:

const request = require("superagent");

// Inserts a new Item
request.post('http://127.0.0.1:5000/api/item')
.send({
  "id_user": "6c67ea36-5bfd-48ec-af62-cede984dff9d",
  "start_date": "2019-04-02",
  "name": "Water Bottle"
})
/* 
   Inserts a new Item, which shouldn't do. Resulting in two items having the
   same date.
*/
request.post('http://127.0.0.1:5000/api/item')
.send({
  "id_user": "6c67ea36-5bfd-48ec-af62-cede984dff9d",
  "start_date": "2019-04-02",
  "name": "Toothpick"
})


Оба будут успешными, чтоне должно быть, поскольку «пользователь» не может иметь два «элемента» в одну и ту же дату.

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

request.post('http://127.0.0.1:5000/api/item') // Inserts a new Item
.send({
  "id_user": "6c67ea36-5bfd-48ec-af62-cede984dff9d",
  "start_date": "2019-04-02",
  "name": "Water Bottle"
})
.then((res) => {
  // It is not successful since there is already an item with that date
  // as expected
  request.post('http://127.0.0.1:5000/api/item') 
  .send({
    "id_user": "6c67ea36-5bfd-48ec-af62-cede984dff9d",
    "start_date": "2019-04-02",
    "name": "Toothpick"
  })
})

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

РЕШЕНИЕ

Я создал сервер Redis.Использовал пакет redis-lock и обернулся вокруг POST маршрута.

var client = require("redis").createClient()
var lock = require("redis-lock")(client);
var itemController = require('./controllers/item');
router.post('/', function(req, res){
  let userId = "";
  if (typeof req.body === 'object' && typeof req.body.id_user === 'string') {
    userId = req.body.id_user;
  }
  lock('POST ' + req.path + userId, async function(done){
    try {
      let result = await itemController.post(req.body)
      res.json(result);
    } catch (e) {
      res.status(500).send("Server Error");
    }
    done()

  })
}

Спасибо.

Ответы [ 2 ]

2 голосов
/ 11 апреля 2019

Объяснять

Это race condition.

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

Что такое состояние гонки?

Решение:

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

Оптимистическая или пессимистическая блокировка
Быстрое решение: pessimistic-lock https://www.npmjs.com/package/redis-lock

1 голос
/ 11 апреля 2019

Вы должны создать составной индекс или составной первичный ключ , который включает поля id_user и start_date.Это обеспечит невозможность создания документов для одного и того же пользователя с одинаковой датой, и база данных выдаст ошибку, если вы попытаетесь это сделать. Составной индекс с мангустом

Вы также можете использовать транзакции .Для этого необходимо выполнить методы find и create внутри транзакции, чтобы гарантировать, что никакие параллельные запросы к одному и тому же документу не будут выполнены. Учебник по транзакциям Mongoose

Больше информации

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

mySchema.index({user_id: 1, start_date: 1}, {unique: true});
...