Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bulkSave promise result, updateResult.getWriteErrorCount is not a function #15314

Closed
2 tasks done
kYem opened this issue Mar 14, 2025 · 7 comments
Closed
2 tasks done

Comments

@kYem
Copy link

kYem commented Mar 14, 2025

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Mongoose version

8.8.2

Node.js version

20.18.3

MongoDB server version

6.10.0

Typescript version (if applicable)

5.5.4

Description

This seems to only happen from time to time, maybe cause we are sending a large list of updates, potentially (1-5100) updates in bulk save.

    const updateResult = await TrackerValue.bulkSave(toUpdateValues);
   // Error updateResult.getWriteErrorCount is not a function
    result.failCount = updateResult.getWriteErrorCount();
    result.success = true;
    return result;

Steps to Reproduce

Unfortunately not easy way to produce. Will try to create separate isolated case, but hopefully even without it would be easy to narrow down when getWriteErrorCount is not a function

Expected Behavior

Should either throw error or TypeScrip types are not correct, as getWriteErrorCount is not always available?

@vkarpov15
Copy link
Collaborator

I'm unable to repro, the following script works as expected:

const mongoose = require('mongoose');

const trackerValueSchema = new mongoose.Schema({ value:{ type: Number, unique: true } });
const TrackerValue = mongoose.model('TrackerValue', trackerValueSchema);

(async () => {
  await mongoose.connect('mongodb://127.0.0.1:27017/test');
  await TrackerValue.deleteMany({});

  const docs = Array.from({ length: 5001 }, (_, i) => new TrackerValue({ value: i }));
  await TrackerValue.insertMany(docs);

  const toUpdateValues = await TrackerValue.find();
  toUpdateValues.forEach((doc) => (doc.value *= 5000));

  const updateResult = await TrackerValue.bulkSave(toUpdateValues);
  console.log(updateResult.getWriteErrorCount());

  await mongoose.disconnect();
})();

Are you passing options to bulkSave()? If you're not passing options to bulkSave() and not using bulkWrite middleware, I'm not sure this particular error is possible. We did find a potential edge case where bulkSave() can theoretically return null, but that wouldn't result in the error message you're seeing. Can you please modify the above repro script to try to demonstrate the issue you're seeing?

@kYem
Copy link
Author

kYem commented Mar 21, 2025

Nope, no options, only array of models to be saved.

  • no middleware for bulkSave

It only happen a few times recently, that Sentry was able to catch, both cases indicate multiple request happening for the same model.

Given that the actual bulkSave can take 3-6s, it's likely that the same underlying documents are being updated in two separate request 🤔. Even though the logs indicate they are sequential.

Image

Note that model is big, have embedded history within the document (so it can grow for frequently updated entries), it also the large collection, over 5 million documents, with a lot of indexes.

So it's not easy to reproduce, and it happens quite rarely as you can see, these are all the entries from Sentry we have.

Image

I'm on holiday right now. I will try to spend more time on reproducible case once I'm back (10 days)

vkarpov15 added a commit that referenced this issue Mar 21, 2025
fix(model): avoid returning `null` from bulkSave() if error doesn't have writeErrors property
@vkarpov15
Copy link
Collaborator

@kYem can you please try upgrading to 8.12.2 and see if that upgrade fixes the issue?

@kYem
Copy link
Author

kYem commented Mar 31, 2025

Managed to reproduce and have two separate queries run, first one finish, second run the same action.

First run, remove user from survey

[] BulkWriteResult {
[]   insertedCount: 0,
[]   matchedCount: 553,
[]   modifiedCount: 553,
[]   deletedCount: 0,
[]   upsertedCount: 0,
[]   upsertedIds: {},
[]   insertedIds: {}
[] }

Second run, remove the same user on same survey
On failed version, the result is

[] {
[]   result: {
[]     ok: 1,
[]     writeErrors: [],
[]     writeConcernErrors: [],
[]     insertedIds: [],
[]     nInserted: 0,
[]     nUpserted: 0,
[]     nMatched: 0,
[]     nModified: 0,
[]     nRemoved: 0,
[]     upserted: []
[]   },
[]   insertedCount: 0,
[]   matchedCount: 0,
[]   modifiedCount: 0,
[]   deletedCount: 0,
[]   upsertedCount: 0,
[]   upsertedIds: {},
[]   insertedIds: {},
[]   n: 0
[] }

This happens when two request trigger a change to the same model.

@vkarpov15 anything come to mind? Is that because the second query does not actually have any updates triggered? as in the first

After updating to 8.12.2, it seems like the issue is fixed and I'm getting

First run

[] BulkWriteResult {
[]   insertedCount: 0,
[]   matchedCount: 553,
[]   modifiedCount: 553,
[]   deletedCount: 0,
[]   upsertedCount: 0,
[]   upsertedIds: {},
[]   insertedIds: {}
[] }

Second Run

[] BulkWriteResult {
[]   insertedCount: 0,
[]   matchedCount: 0,
[]   modifiedCount: 0,
[]   deletedCount: 0,
[]   upsertedCount: 0,
[]   upsertedIds: {},
[]   insertedIds: {},
[]   n: 0,
[]   mongoose: { validationErrors: [], results: [] }
[] }

Note that object is still different, but the underlying class is the same now, therefore there is no longer error.

Is that what you expected @vkarpov15 ?

@kYem
Copy link
Author

kYem commented Mar 31, 2025

Now it can be quite easily reproduced, by just removing update on your original script

const mongoose = require('mongoose');

const trackerValueSchema = new mongoose.Schema({ value:{ type: Number, unique: true } });
const TrackerValue = mongoose.model('TrackerValue', trackerValueSchema);

(async () => {
  await mongoose.connect('mongodb://127.0.0.1:27017/test');
  await TrackerValue.deleteMany({});

  const docs = Array.from({ length: 5001 }, (_, i) => new TrackerValue({ value: i }));
  await TrackerValue.insertMany(docs);

  const toUpdateValues = await TrackerValue.find();
  // No longer doing changes, just saving
  //  toUpdateValues.forEach((doc) => (doc.value *= 5000));

  const updateResult = await TrackerValue.bulkSave(toUpdateValues);
  console.log(updateResult.getWriteErrorCount());

  await mongoose.disconnect();
})();

@kYem
Copy link
Author

kYem commented Mar 31, 2025

Additional note, it seems this was no longer an issue since version 8.10.2, I assume likely it fixed as part of https://github.com/Automattic/mongoose/releases/tag/8.10.2

@vkarpov15 vkarpov15 added this to the 8.13.2 milestone Mar 31, 2025
@vkarpov15
Copy link
Collaborator

Confirmed that this was fixed in 8.10.2, so I will close this issue.

@vkarpov15 vkarpov15 removed this from the 8.13.2 milestone Apr 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants