skip to Main Content

When attempting to create multiple document inside a mongoose transaction, encountering a 11000 duplicate key error auto abort’s the transaction, even if the error is swallowed in a try / catch block. Any other error does not abort.

I would like for mongoose to attempt to create all the documents regardless of duplicate error, so that I can provide the user with all the possible errors rather than one error per request.

I have tried creating the documents in a for of loop like so:

try {
  session.startTransaction();
  for (const document of documents) {
    try {
      const newDocument = await Document.create([document], { session });
    } catch (error) {
      switch (error.code) {
        case 11000:
          // I would like to swallow 11000 and push error to array
          const { keyValue } = error;
          const duplicateError = createDuplicateError(keyValue);
          duplicateErrors.push(duplicateError);
          break;
        case 251:
          break;
        default:
          throw error;
      }
    }
  }
  await session.commitTransaction();
} catch (error) {
  // Transaction failed
  await session.abortTransaction();
  throw error;
} finally {
  await session.endSession();
}

// Here I can throw error for user with all duplicate errors
if (duplicateErrors.length > 0) throw new Error("Duplicate duplicateErrors");

If there is an 11000 error in the loop the next iteration will throw a 251 ‘NoSuchTransaction’ error when attempting the next create.

I have also tried unordered bulkWrite operation, however according to the docs

Inside a transaction, the first error in a bulk write causes the entire bulk write to fail and aborts the transaction, even if the bulk write is unordered.

Mongodb Bulk Write Transaction Docs

Is it possible to change the default behavior of bulkWrite inside a transaction, to ignore errors, or else swallow the error when looping over separate create operations?

I get that in write operations of millions of docs, it would not be ideal to continue attempting to create docs if the transaction was guaranteed to fail, but in my case the number of docs will be few, and be more beneficial to provide the user with all errors in one request rather tha requiring multiple requests for each error

2

Answers


  1. Chosen as BEST ANSWER

    Eventually got this working by creating the below function. Maybe it will help someone

      async createDocumentsFromArray(documentArray, arrayName, model, uniqueErrors, session) {
        const array = [];
        const errors = [];
    
        const tryArrayTransaction = async (session, index = 0) => {
          for (let i = index; i < documentArray.length; i++) {
            try {
              const [document] = await model.create([documentArray[i]], { session });
              array.push(document);
            } catch (error) {
              switch (error.code) {
                case 11000:
                  const { keyValue } = error;
                  const data = Object.fromEntries(
                    Object.keys(keyValue).map((key) => [`${arrayName}[${i}].${key}`, uniqueErrors[key]])
                  );
                  errors.push(data);
                  break;
                case 251:
                  await session.abortTransaction();
                  session.startTransaction();
                  await tryArrayTransaction(session, i);
                  break;
                default:
                  break;
              }
            }
          }
        };
    
        await tryArrayTransaction(session);
    
        if (errors.length > 0) {
          throw new UserInputError("Validation Failed", { data: _.merge({}, ...errors) });
        }
    
        return array;
      }
    

  2. I had the same issue. Searched for a built-in answer to no avail. My solution was to add a custom validator to ensure that those duplicate docs are not actually sent to the DB, and are caught on the server side.

    schema.pre("validate", async function () {
        const refs = await Model.find({ ... }).count()
    
        if (refs > 0) {
            throw new Error("Some error message")
        }
    })
    

    Then in your insertMany result, you should get a list of validation errors, wherefrom you can grab the docs that were duplicates.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search