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

Collections/Globals with hooks that call await payload.update() hang on save with Postgres #8852

Closed
azivkovi opened this issue Oct 24, 2024 · 11 comments

Comments

@azivkovi
Copy link
Contributor

azivkovi commented Oct 24, 2024

Describe the Bug

I use afterChange and beforeChange hooks to update other collections when saving an item. When I use await req.payload.update() to do so, "Submitting..." never finishes and my app hangs. When I exclude the req.payload.update() logic, hooks finish as expected and item is saved.

This happens only with Postgres, not Mongodb, and I noticed it affects both Collections and Globals.
It also happens if i load the payload instance as const payload = await getPayloadHMR({ config: configPromise }), instead of using req.

Link to the code that reproduces this issue

https://github.com/azivkovi/payload3demo

Reproduction Steps

  1. Initialize the linked repo.
  2. Set up a test postgres database and edit the .env file for database url.
  3. Enter Pages collection and add/edit an item - everything works as expected, as it has no hooks that call await req.payload.update().
  4. Enter Users collection or Settings global and add/edit an item - app hangs on save as they have await req.payload.update() in a hook.

Which area(s) are affected? (Select all that apply)

db-postgres

Environment Info

Node.js v22.9.0

Binaries:
Node: 22.9.0
npm: 10.8.2
Yarn: N/A
pnpm: 9.12.1
Relevant Packages:
payload: 3.0.0-beta.118
next: 15.0.0
@payloadcms/db-mongodb: 3.0.0-beta.118
@payloadcms/db-postgres: 3.0.0-beta.118
@payloadcms/graphql: 3.0.0-beta.118
@payloadcms/next/utilities: 3.0.0-beta.118
@payloadcms/richtext-lexical: 3.0.0-beta.118
@payloadcms/richtext-slate: 3.0.0-beta.118
@payloadcms/translations: 3.0.0-beta.118
@payloadcms/ui/shared: 3.0.0-beta.118
react: 19.0.0-rc-65a56d0e-20241020
react-dom: 19.0.0-rc-65a56d0e-20241020
Operating System:
Platform: linux
Arch: x64
Version: #1 SMP Fri Mar 29 23:14:13 UTC 2024
Available memory (MB): 31970
Available CPU cores: 24

@azivkovi azivkovi added status: needs-triage Possible bug which hasn't been reproduced yet v3 labels Oct 24, 2024
@r1tsuu
Copy link
Member

r1tsuu commented Oct 24, 2024

Try to pass the req here to use the transaction

await req.payload.update({
  collection,
  where: {
    id: {
      in: [1, 2, 3, 5, 6, 7],
    },
  },
  data: {
    featured: true,
  },
})

@azivkovi
Copy link
Contributor Author

I did

await req.payload.update({
  collection,
  where: {
    id: {
      in: [1, 2, 3, 5, 6, 7],
    },
  },
  data: {
    featured: true,
  },
  req
})

This didn't fix the problem and it still hangs, but the hook is now called repeatedly from what I can see in the console log.

@DanRibbens
Copy link
Contributor

It also happens if i load the payload instance as const payload = await getPayloadHMR({ config: configPromise }), instead of using req.

This is not recommended. You should use the req passed to the hooks.

The reason this doesn't work is because PostgreSQL transactions are meant to be run sequentially. You'll need to either not use transactions or await each call synchronously.

@github-actions github-actions bot removed the status: needs-triage Possible bug which hasn't been reproduced yet label Oct 24, 2024
@azivkovi
Copy link
Contributor Author

azivkovi commented Oct 24, 2024

I do use req, I just noted that it also happens if I do not.

Did you not see my code? All update calls are awaited.

Sorry, but I am not sure on what grounds you closed the issue on. Can you please elaborate on your answer?

@DanRibbens
Copy link
Contributor

DanRibbens commented Oct 24, 2024

Oh I was thinking that with the for loop they'd be called asynchronously, but that was my mistake.

I'll reopen and keep the discussion going.

@DanRibbens DanRibbens reopened this Oct 24, 2024
@github-actions github-actions bot added the status: needs-triage Possible bug which hasn't been reproduced yet label Oct 24, 2024
@DanRibbens
Copy link
Contributor

In the payload update operation that handles multiple documents, we use promise.all for all docs that match from the where. That is the reason the hook is hanging. We need to revisit if that is the right approach. This tells me we probably also don't have test coverage because I wouldn't expect any update many that interact with multiple docs to work in postgres based on this.

@r1tsuu
Copy link
Member

r1tsuu commented Oct 24, 2024

I wonder if we should implement updateMany to the db as we have with deleteMany. I imagine sequential execution for update without this can be very slow

@DanRibbens
Copy link
Contributor

The problem with adding updateMany is that not all documents will have the same update applied due to hooks. Suppose you have a beforeChange hook for a field that is dynamic. Each document, having a different value for that particular dynamic field requires a separate db.updateOne to be called.

Even if we have a db.updateMany that somebody could use in their hooks, I wouldn't recommend that either. It would bypass all the Payload logic like validation, access control, saving versions, handling of localization, and all hooks.

@azivkovi
Copy link
Contributor Author

azivkovi commented Nov 7, 2024

Hooks are practically unusable for updating collections this way. Is there any other approach you would suggest or is this the way, it just needs to be fixed?

Just to add new info, the same problem occurs when updating a single item by id, not just when it's update multiple:

  await req.payload.update({
    collection: 'modules',
    id: moduleId,
    data: {
      averageScore: averageModuleScore,
    },
  });

This will cause the collection afterChange hook to hang.

@DanRibbens
Copy link
Contributor

As Sasha pointed out, you really should be passing req to your functions so that it can work within a transaction:

await req.payload.update({
    collection: 'modules',
    id: moduleId,
    req, // added to your query
    data: {
      averageScore: averageModuleScore,
    },
  });

the hook is now called repeatedly

I just read your example repo again and the commented out hooks you have appear to have the exact problem. Remember, each time you call update you're going to have more hooks run.

When users are updated you want to also call update on pages and users according to this [hook](https://github.com/azivkovi/payload3demo/blob/ea7ab61f531ceef2e52987618b7ace8d0b0e597e/payload.config.ts#L81.
I would expect this to never complete and endlessly call updates to pages and users based on what I'm reading.

You need a way to bypass the update on subsequent calls. You can do that by either calling db methods directly (knowing you are also bypassing all validation/hooks/version creation/etc) or you can do add your own flag on req.context so that the hook can check if it has already run and return early instead.

We could add a db.updateMany in the future, but I don't know that there is a place in the code we need that.

I'm going to close this issue since I believe that is all it is.

Thanks!

@DanRibbens DanRibbens closed this as not planned Won't fix, can't repro, duplicate, stale Nov 7, 2024
@github-actions github-actions bot removed the status: needs-triage Possible bug which hasn't been reproduced yet label Nov 7, 2024
Copy link
Contributor

github-actions bot commented Nov 9, 2024

This issue has been automatically locked.
Please open a new issue if this issue persists with any additional detail.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 9, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants