简体   繁体   中英

Creating a linked list in MongoDB (Mongoose) with transactions

I want to create a singly linked list of Mongo documents consistent; ie, I want each document to only 1 parent a 1 child, and I want there to be no loops. I also want there to be only 1 root. I tried accomplishing this with transactions, but it doesn't seem to actually be blocking properly.

const session = await mongoose.startSession();
session.startTransaction();
       
const message = await Message
    .findOne({ root: root })
    .sort({ time: -1 })
    .session(session);
            
const newMessage = await Message.create(
    {
        time: new Date(),
        root: root,
        previous: message,
        content: content,
    },
    { session: session }
);

await session.commitTransaction();
session.endSession();

Here, I'm trying to get the latest message, and create a new message with the latest message set as the previous, as to create a linked list of messages. But when I call this in rapid succession (ie, have multiple clients try posting messages at the same time), it apparently fails, because it ends up creating messages with no a null previous field; it seems like it's running the transactions in parallel, instead of in series. It should wait to do the findOne until after the current transaction completes, but it looks like it's just running all the findOne 's and then running all the create 's. This leads me to believe that I must have an issue with the way I'm doing the transactions, but I have no clue what I'm doing wrong.

For reference, here is the collection after this code has been run with 10 clients.

rs0:PRIMARY> db.messages.find()
{ "_id" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d43"), "time" : ISODate("2020-08-31T23:04:08.347Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b2"), "content" : "Hi guys, my name's 6. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d44"), "time" : ISODate("2020-08-31T23:04:08.350Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b4"), "content" : "Hi guys, my name's 5. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d45"), "time" : ISODate("2020-08-31T23:04:08.351Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b3"), "content" : "Hi guys, my name's 9. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d46"), "time" : ISODate("2020-08-31T23:04:08.356Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781ae"), "content" : "Hi guys, my name's 1. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d47"), "time" : ISODate("2020-08-31T23:04:08.369Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781ad"), "content" : "Hi guys, my name's 2. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d48"), "time" : ISODate("2020-08-31T23:04:08.371Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b5"), "content" : "Hi guys, my name's 7. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d49"), "time" : ISODate("2020-08-31T23:04:08.374Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b1"), "content" : "Hi guys, my name's 8. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d4a"), "time" : ISODate("2020-08-31T23:04:08.377Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781b0"), "content" : "Hi guys, my name's 3. Nice to meet you.", "photos" : [ ], "__v" : 0 }
{ "_id" : ObjectId("5f4d81e8e86a0c1a4fd93d4b"), "time" : ISODate("2020-08-31T23:04:08.380Z"), "root" : ObjectId("5f4d81e7e86a0c1a4fd93d38"), "previous" : null, "from" : ObjectId("5f4d81e49497e816b74781af"), "content" : "Hi guys, my name's 4. Nice to meet you.", "photos" : [ ], "__v" : 0 }

EDIT: One of the comments pointed out that I wasn't using transactions; I updated the code to use transactions. However, the new code still doesn't work.

You can create a unique partial index on the previous field, excluding those documents from the index that don't have that field.

db.messages.createIndex(
      {previous: 1}, 
      {unique: true, partialFilterExpression: {previous: {$exists:true}}}
)

Any insert or update that attempts to set a value for previous that has already been used will get "E11000 duplicate key error".

If the inserting process receives that error, it knows that something else is already linked to what it was about to, so it needs to check again to find the new tail of the list.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM