parse-server icon indicating copy to clipboard operation
parse-server copied to clipboard

Increment does not appear to be atomic

Open theolundqvist opened this issue 1 year ago • 12 comments

New Issue Checklist

Issue Description

obj.increment does not appear to be atomic.

Steps to reproduce

I have the following code in the beforeSave hook of "childClass" and child has a pointer "parent" to "parentClass".

await obj.get("parent").increment("number_children").save(null, {
    useMasterKey: true,
});

Actual Outcome

Creating two child objects simultaneously results in "number_children" increasing from 0 to 1. This does not happen with:

await obj1.save();
await obj2.save();

but only with

await Promise.all(obj1.save(), obj2.save());

Expected Outcome

I expected the count to increase from 0 to 2.

Environment

Server

  • Parse Server version: 6.2.2
  • Operating system: MacOS Ventura
  • Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): local

Database

  • System (MongoDB or Postgres): MongoDB
  • Database version: 6.0.10
  • Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): MongoDB Atlas

Client

  • SDK (iOS, Android, JavaScript, PHP, Unity, etc): js
  • SDK version: 4.2.0

Logs

theolundqvist avatar Oct 08 '23 18:10 theolundqvist

Thanks for opening this issue!

  • 🚀 You can help us to fix this issue faster by opening a pull request with a failing test. See our Contribution Guide for how to make a pull request, or read our New Contributor's Guide if this is your first time contributing.

Could you add a test case with that example that you already posted?

mtrezza avatar Oct 22 '23 21:10 mtrezza

I will have to examinate if I am doing anything special on my side. The following test works:

  fit('regression test for #8772 (increment should be atomic))', async () => {
    Parse.Object.disableSingleInstance();

    Parse.Cloud.beforeSave('Parent', (req) => {});
    Parse.Cloud.beforeSave('Child', async (req) => {
      await req.object.get("parent").increment("num_child").save(null, {useMasterKey:true})
    });

    let parent = await new Parse.Object('Parent').save(null, {useMasterKey:true});
    
    const child = () => new Parse.Object('Child').set("parent",parent).save();

    // add synchronously
    await child()
    await child()
    parent = await parent.fetch();
    expect(parent.get('num_child')).toBe(2);

    // add asynchronously
    await Promise.all(Array.from({length: 40}, () => child()))
    parent = await parent.fetch();

    expect(parent.get('num_child')).toBe(42);

theolundqvist avatar Oct 24 '23 06:10 theolundqvist

I's be surprised if no atomic test already exists. If there really is none we can add your test in any case. Could you open a PR?

mtrezza avatar Oct 24 '23 09:10 mtrezza

Actually I can't find any. I'll open a PR

theolundqvist avatar Oct 24 '23 17:10 theolundqvist

Yea even for me the updates are not atomic

RahulLanjewar93 avatar Nov 23 '23 04:11 RahulLanjewar93

Yea even for me the updates are not atomic

Could you elaborate on this? The test passes. Can you provide an example?

theolundqvist avatar Nov 23 '23 06:11 theolundqvist

Yea even for me the updates are not atomic

Could you elaborate on this? The test passes. Can you provide an example?

Yea i tried as well the test you added is passing. I am using multiple instances of parse server and there are cases where I am updating the object from all the servers at sa,e time using increment op. But it seems the increment isn't atomic when using multiple instances.

RahulLanjewar93 avatar Nov 23 '23 07:11 RahulLanjewar93

@RahulLanjewar93 Why could it be working in parse-server tests but not in our own code?

Did you run the test on your own infrastructure?

The increment operation should be sent directly to mongo so therefore it should always be atomic independent of how many instances you are using? I am only using one instance

theolundqvist avatar Nov 23 '23 09:11 theolundqvist

Yea, increment ops in mongo are atomic. I had tested it before in my own infrastructure where i was using multiple instances. Currently I am occupied with something else, i'll have a test again and update on the same.

RahulLanjewar93 avatar Nov 23 '23 10:11 RahulLanjewar93

But it seems the increment isn't atomic when using multiple instances.

Although I haven't looked, I doubt that there is anything instance specific in the request that is sent to the DB. Maybe it's a DB issue? Or maybe it's an infrastructure issue where requests are lost, time out, or similar?

Or maybe it is a more complex Parse JS SDK issue where multiple increments / decrements, and object re-fetches in between cause issues with caching? You could add a more complex test to the https://github.com/parse-community/parse-server/pull/8789 where you randomly increment / decrement / re-fetch an object in a loop and check whether the end result is still correct.

mtrezza avatar Mar 25 '24 00:03 mtrezza

@RahulLanjewar93 Do you have any update on the issue? You mentioned you observed the same issue, but the test in #8789 passed, so I wonder if this issue here can be closed?

mtrezza avatar Jul 09 '24 00:07 mtrezza