0

Performing >10 requests, at least, at the same time, this code inserting 4 same records, but must insert only 1.

User could start only 1 active game. (in active game field finished = false)

console.log('performing request');
const activeGame = await db.Game.findOne({ uid, finished: false });
if (activeGame !== null) {
    console.log('errored');
    return { error: 'Finish previous game.' };
} else {
    console.log('inserted');
    const game = new db.Game({
        uid,
        moves: new Array(5*5).fill(0)
    });
    game.save();
    return game;
}

Logs while 10 request at the same time:

performing request
inserted
performing request
performing request
inserted
performing request
inserted
inserted
performing request
errored
performing request
errored
performing request
errored
performing request
errored
performing request
performing request
errored
errored

Transactions don't help me.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189

2 Answers2

0

Of course, this code is prone to race conditions. To avoid this, you can use All you need is a partial unique index. Apply it to uid field for documents where finished: false et voila, you now can't insert two active games for the same user.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • How to use it in insertion? – kolja cheesch Nov 06 '19 at 11:09
  • 1
    @koljacheesch: documentation has examples. Look for `upsert` option – Sergio Tulentsev Nov 06 '19 at 11:09
  • I already had read this one, but I can't understand how to insert, using this function. Also I wrote in this question a small part of code, activeGame find happening in other place (in middleware), but insertion happening in game.js. Also I don't know how to split this function. If do the same code it gonna spoil project structure... – kolja cheesch Nov 06 '19 at 11:13
  • @koljacheesch: if I have time later today, I'll try to make an example. As for your other problem (find and update happening in different place): maaaaybe you can solve it by creative application of unique indexes. But more likely you'll have to rethink this design. – Sergio Tulentsev Nov 06 '19 at 11:21
  • Thank you. Well, I need a little bit other logic, I don't need update at all, I need findOneAndInsert, instead findOneAndUpdate... – kolja cheesch Nov 06 '19 at 11:25
  • @koljacheesch: this function does not exist because it does not make sense. :) "find one and insert". Either it exists and can be found or it doesn't and needs to be inserted. Can't do both. But you can bend `findOneAndUpdate` to do this, with the `upsert` flag. – Sergio Tulentsev Nov 06 '19 at 11:32
  • Yes, but If active game already exists, and user gonna make one more, it will update game (not insert), and game will rewrite current state to initial paramaters. – kolja cheesch Nov 06 '19 at 11:34
  • @koljacheesch: that depends on your search criteria and update parameters. – Sergio Tulentsev Nov 06 '19 at 11:36
  • Well, this gonna change user bet when user try to create a game with having not finished one: create: async (uid, bet) => { try { const minesweeper = await db.Minesweeper.findOneAndUpdate( { uid, winning: null }, { uid, bet, bombs: new Array(5*5).fill(0) }, { upsert: true } ); return minesweeper } catch (e) { return { error: 'Server error' } } } – kolja cheesch Nov 06 '19 at 11:41
  • @koljacheesch First, even with `findOneAndUpdate` with upsert, it is still possible for two or more upserts to have a race condition. You have UUID in the document. If multiple threads share the same UUID, you will have a race condition no matter what. My opinion, I don't think you're testing the right thing. I think you should prevent one UUID to be shared across threads. – kevinadi Nov 06 '19 at 23:45
  • @koljacheesch Second, databases are designed to allow high concurrency. What you want to do here is to lock the database, insert one document, then unlock it to prevent race condition. This is essentially single-threading the database. Eventually this design will hit a performance wall that you cannot solve. As before, I suggest you to not allow a single UUID to be shared across threads. – kevinadi Nov 06 '19 at 23:47
  • @kevinadi, kolja After a good night of sleep and an espresso, I realized: you don't even need `findOneAndUpdate`. All you need is a [partial unique index](https://docs.mongodb.com/manual/core/index-partial/#partial-index-with-unique-constraints)! Apply it to uid where `finished: false` et voila, you now can't insert two active games for the same user. – Sergio Tulentsev Nov 07 '19 at 09:30
-1

Your uid is different so it will insert these times. If you will pass the same uid then it will work according to you.

You can try these. and read save() functionality.

console.log('performing request');
const activeGame = await db.Game.findOne({ _id: uid, finished: false });
if (activeGame == null) {
      console.log('inserted');
    const game = new db.Game({
        moves: new Array(5*5).fill(0)
    });
    game.save();
    return game;
} else {
    console.log('errored');
    return { error: 'Finish previous game.' };
}

OR

console.log('performing request');
const activeGame = await db.Game.findOne({ _id: uid, finished: false });
activeGame.save()
Ankit Kumar Rajpoot
  • 5,188
  • 2
  • 38
  • 32