How to avoid two concurrent API requests breaking the logic behind document validation?

♀尐吖头ヾ 提交于 2021-02-10 18:56:21

问题


I have an API that in order to insert a new item it needs to be validated. The validation basically is a type validator(string, number, Date, e.t.c) and queries the database that checks if the "user" has an "item" in the same date, which if it does the validation is unsuccessful.

Pseudocode goes like this:

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;


}

My problem is if I do two concurrent requests like this:

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"
})


Both will be successful, which it shouldn't be since an "user" cannot have two "items" in the same date.

If I execute the second one after the first is finished, everything works as expected.

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"
  })
})

To avoid this I send one request with an array of documents, but I want to prevent this issue or at least make less likely to happen.

SOLUTION

I created a redis server. Used the package redis-lock and wrapped around the POST route.

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()

  })
}

Thank you.


回答1:


Explain

That is a race condition.

two or more threads can access shared data and they try to change it at the same time

What is a race condition?

Solution:

There are many ways to prevent conflict data in this case, a lock is 1 option.
You can lock on application level or database level... but I prefer you read this thread before chose any of them.

Optimistic vs. Pessimistic locking
Quick solution: pessimistic-lock https://www.npmjs.com/package/redis-lock




回答2:


You should create a composite index or a composite primary key that includes the id_user and the start_date fields. This will ensure that no documents for the same user with the same date can be created, and the database will throw an error if you'll try to do it. Composite index with mongoose

You could also use transactions. To do it, you should execute the find and the create methods inside a transaction, to ensure that no concurrent queries on the same document will be executed. Mongoose transactions tutorial

More infos

I would go with an unique composite index, that in your specific case should be something like

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


来源:https://stackoverflow.com/questions/55629269/how-to-avoid-two-concurrent-api-requests-breaking-the-logic-behind-document-vali

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!